• 《Java并发编程的艺术》第5章 Java中的锁(下)


    目录

    AQS

    获取锁失败,入队,加入到同步队列尾部

    头节点获取锁成功,释放锁时,头节点将出队

    重入锁ReentrantLock

    重入锁的特性

    • 可重入

    • 公平性获取锁

    1.可重入实现原理

    同步状态(可认为就是锁) state 表示锁被一个线程重复获取的次数

    • state == 0 表示当前锁未被线程持有

    • state !=0 计数器,表示锁被某个线程重进入的次数

    1. 锁的获取

    如果尝试获取锁的线程为当前持有锁的线程,可再次获取成功,并将计数器加1。

    final boolean nonfairTryAcquire(int acquires) {
      final Thread current = Thread.currentThread();
      int c = getState();
      if (c == 0) {
        if (compareAndSetState(0, acquires)) {
          setExclusiveOwnerThread(current);
          return true;
        }
      }
      //如果当前尝试获取锁的线程为持有锁的线程,可再次获取成功,并将计数器加1。
      else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
          throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
      }
      return false;
    }
     

    2. 锁的释放

    protected final boolean tryRelease(int releases) {
      //释放锁时,将计数器减1.
      int c = getState() - releases;
      if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
      boolean free = false;
      //如果计数器的值变为0,将持有锁的线程设为null,表示锁未被持有
      if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
      }
      setState(c);
      return free;
    }

    2.公平锁与非公平锁实现原理

    非公平锁

    final boolean nonfairTryAcquire(int acquires) {
      final Thread current = Thread.currentThread();
      int c = getState();
      if (c == 0) {
        if (compareAndSetState(0, acquires)) {
          setExclusiveOwnerThread(current);
          return true;
        }
      }
      //如果当前尝试获取锁的线程为持有锁的线程,可再次获取成功,并将计数器加1。
      else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
          throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
      }
      return false;
    }
     

    公平锁

    protected final boolean tryAcquire(int acquires) {
      final Thread current = Thread.currentThread();
      int c = getState();
      if (c == 0) {
        //与非公平锁的不同,多了!hasQueuedPredecessors() 判断条件,会首先判断同步队列中是否等待的线程
        //公平锁:同队队列中有等待的线程,获取失败,插入同步队列尾部
        //非公平锁:直接尝试获取锁,获取失败,插入同步队列尾部
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
          setExclusiveOwnerThread(current);
          return true;
        }
      }
      else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
          throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
      }
      return false;
    }
     

    读写锁ReadWriteLock

    什么是读写锁

    • 读锁:共享锁,可以多个线程同时持有。约束条件:当读锁被持有,后续线程只能获取读锁。

    • 写锁:排他锁,同一时刻只能同一个线程持有。约束条件:当写锁被持有,后续获取读锁或写锁的线程都将被阻塞。

    1.读写状态的设计

    将同步状态state“按位切割使用”。

    高16位为读状态,表示读锁被获取的次数;低16位为写状态,表示写锁被获取的次数。

    读写锁支持重进入,以读写线程为例:读线程获取读锁之后,能够再次获取读锁;写线程获取写锁后,能够再次获取写锁,也能够获取读锁。

    以上状态表示某线程获取了写锁3次,获取了读锁2次。

    2.写锁的获取与释放

    2.1写锁的获取

    每次写锁获取成功时将写状态加1。

    据读写锁的约束条件,获取写锁时有如下限制:

    1)如果读锁被持有了,无法获取写锁

    2)如果读锁没有没持有,但是写锁的持有线程不是当前线程,无法获取写锁

     protected final boolean tryAcquire(int acquires) {
                /*
                 * Walkthrough:
                 * 1. If read count nonzero or write count nonzero
                 *    and owner is a different thread, fail.
                 * 2. If count would saturate, fail. (This can only
                 *    happen if count is already nonzero.)
                 * 3. Otherwise, this thread is eligible for lock if
                 *    it is either a reentrant acquire or
                 *    queue policy allows it. If so, update state
                 *    and set owner.
                 */
                Thread current = Thread.currentThread();
                int c = getState();
                //获取写锁的获取次数
                int w = exclusiveCount(c);
                /**
                * c!=0,说明读锁或写锁被占有
                */
                if (c != 0) {
                    // (Note: if c != 0 and w == 0 then shared count != 0)
                    /**
                     * 存在读锁或者当前线程不是已经获取写锁的线程,获取写锁失败
                     */
                    if (w == 0 || current != getExclusiveOwnerThread())
                        return false;
                    if (w + exclusiveCount(acquires) > MAX_COUNT)
                        throw new Error("Maximum lock count exceeded");
                    // Reentrant acquire
                    setState(c + acquires);
                    return true;
                }
                /**
                 * c==0,尝试获取写锁,获取成功后将写状态+1
                 */
                if (writerShouldBlock() ||
                    !compareAndSetState(c, c + acquires))
                    return false;
                setExclusiveOwnerThread(current);
                return true;
            }

    2.2写锁的释放

    每次写锁释放成功时将写状态减1,当写状态为0时,表示写锁被释放,从而等待的读写线程能够继续访问读写锁。

    3.读锁的获取与释放

    3.1读锁的获取

    每次读锁获取成功时将写状态加1。

    据读写锁的约束条件,获取读锁时有如下限制:

    如果其他线程获取了写锁,则当前线程获取读锁失败。

    (说明:读状态是所有线程获取读锁的次数总和,每个线程各自获取读锁的次数保存在ThreadLocal中,由线程自己维护。以下代码做了精简,仅保留了必要的部分)

    protected final int tryAcquiredShared(int unused) {
            for (;;) {
                int c = getState();
                int nextc = c + (1 << 16);
                if (nextc < c) {
                    throw new Error("Maxinum lock count exceeded");
                }
                /**
                * 如果其他线程获取了写锁,当前线程获取写锁失败。
                */
                if (exclusiveCount(c) != 0 && owner != Thread.currentThread()) {
                    return -1;
                }
                if (compareAndSetState(c, nextc)) {
                    return 1;
                }
            }
        }
     

    3.2读锁的释放

    每次读锁释放成功时将读状态减1。

    Condition实现原理

    什么是Condition

    Condition接口提供了await()、signal()、signalAll()等方法,配合Lock可以实现等待/通知模式。比如用于实现生产者/消费者。

    1.等待队列

    每个Condition对象都维持着一个等待队列,维持着当前等待在当前condition对象上的线程队列。

    一个Lock可对应多个Condition对象,每个condition对象维持一个等待队列。

    2.等待(await()方法)

    调用await方法(或以await开头的方法),线程将释放锁,加入到等待队列的尾部。

    从同步队列和等待队列的角度来看,调用await()方法时将释放锁,导致节点从同步队列移除,然后构造节点加入到等待队列找中。

    整个过程相当于同步队列的首节点(获取了锁的节点)移动到等待队列尾部。

    3.通知(signal()、signalAll()方法)

    调用signal()方法,将会唤醒在等待队列中等待时间最长的节点(首节点)。

    从同步队列和等待队列的角度来看,被唤醒的线程将加入到同步队列中,一旦线程获取锁成功,将从await()方法返回。

    调用signal()方法,相当于对等待队列中的每一个线程执行了signal()方法,相当于将等待队列中的所有节点都移动到同步队列中。

    (顺序问题:如果为非公平模式,所有从等待队列中唤醒的线程都将先尝试获取锁,如果获取失败才加入到同步队列中)

     

  • 相关阅读:
    数据库 连接(join)
    Linux top
    Game2048
    黑豆白豆问题
    1000个苹果10箱
    Jconsole
    八数码 Java实现
    两数之和
    磁盘调度算法
    常见应用网络层次
  • 原文地址:https://www.cnblogs.com/yeyang/p/12580956.html
Copyright © 2020-2023  润新知