死磕Java并发-JUC锁核心类AQS详解

1,165 阅读16分钟

这是我参与 8 月更文挑战的第 5 天

往期推荐

一、AQS简介

AQSAbstractQueuedSynchronizer,即抽象的队列同步器,是通过维护一个共享资源状态(Volatile Int State)和一个先进先出(FIFO)的线程等待队列来实现一个多线程访问共享资源的同步框架。

基于AQS构建同步器:

  • ReentrantLock

  • Semaphore

  • CountDownLatch

  • ReentrantReadWriteLock

  • SynchronusQueue

  • FutureTask 优势:

  • AQS 解决了在实现同步器时涉及的大量细节问题,例如自定义标准同步状态、FIFO 同步队列。

  • 基于 AQS 来构建同步器可以带来很多好处。它不仅能够极大地减少实现工作,而且也不必处理在多个位置上发生的竞争问题。

二、AQS的核心知识

2.1 AQS的核心思想

AQS(AbstractQueuedSynchronizer)核心思想是:如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制 AQS 是用 CLH 队列锁实现的,即将暂时获取不到锁的线程加入到队列中。

CLH(Craig,Landin,and Hagersten) 队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS 是将每条请求共享资源的线程封装成一个 CLH 锁队列的一个结点 Node 来实现锁的分配。 AQS使用一个int成员变量来表示同步状态,通过内置的FIFO队列来完成获取资源线程的排队工作。AQS使用CAS对该同步状态进行原子操作实现对其值的修改。

private volatile int state;//共享变量,使用volatile修饰保证线程可见性

状态信息通过procted类型的getState,setState,compareAndSetState进行操作

//返回同步状态的当前值
protected final int getState() {  
        return state;
}
 // 设置同步状态的值
protected final void setState(int newState) { 
        state = newState;
}
//原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值)
protected final boolean compareAndSetState(int expect, int update) {
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

2.2 AQS的数据结构

CLH队列图

30.png

  • Sync queue:  同步队列,是一个双向列表。包括head节点和tail节点。head节点主要用作后续的调度。
  • Condition queue:  非必须,单向列表。当程序中存在cindition的时候才会存在此列表。

29.png

2.3 AQS底层使用了模板方法模式

同步器的设计是基于模板方法模式的,也就是说,使用者需要继承同步器并重写指定的方法,随后将同步器组合在自定义同步组件的实现中,并调用同步器提供的模板方法,而这些模板方法将会调用使用者重写的方法。

同步器可重写的方法如下表所示。

方法名称描述
protected boolean tryAcquire(int arg)独占式获取同步状态,实现该方法需要查询当前状态并判断同步状态是否符合预期,然后再进行 CAS 设置同步状态
protected boolean tryRelease(int arg)独占式释放同步状态,等待获取同步状态的线程将有机会获取同步状态
protected int tryAcquireShared(int arg)共享式获取同步状态,返回大于等于 0 的值,表示获取成功,反之,获取失败
protected boolean tryReleaseShared(int arg)共享式释放同步状态
protected boolean isHeldExclusively()当前同步器是否在独占模式下被线程占用,一般该方法表示是否被当前线程所独占

默认情况下,每个方法都抛出 UnsupportedOperationException。 这些方法的实现必须是内部线程安全的,并且通常应该简短而不是阻塞。AQS类中的其他方法都是final ,所以无法被其他类使用,只有这几个方法可以被其他类使用。

同步器提供的模板方法分为 3 类:独占式获取与释放同步状态共享式获取与释放同步状态查询同步队列中的等待线程情况

31.png

2.4 AQS对资源的共享方式

AQS定义两种资源共享方式

  • Exclusive(独占):只有一个线程能执行,如ReentrantLock。又可分为公平锁和非公平锁:

    • 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁
    • 非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的
  • Share(共享):多个线程可同时执行,如Semaphore/CountDownLatch。Semaphore、CountDownLatCh、 CyclicBarrier、ReadWriteLock 我们都会在后面讲到。

ReentrantReadWriteLock 可以看成是组合式,因为ReentrantReadWriteLock也就是读写锁允许多个线程同时对某一资源进行读写。

不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源 state 的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在上层已经帮我们实现好了。

2.5 AQS的类结构

2.5.1 类的继承关系

AbstractQueuedSynchronizer继承自AbstractOwnableSynchronizer抽象类,并且实现了Serializable接口,可以进行序列化。

public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable

其中AbstractOwnableSynchronizer抽象类的源码如下:

public abstract class AbstractOwnableSynchronizer implements java.io.Serializable {
    
    // 版本序列号
    private static final long serialVersionUID = 3737899427754241961L;
    // 构造方法
    protected AbstractOwnableSynchronizer() { }
    // 独占模式下的线程
    private transient Thread exclusiveOwnerThread;
    
    // 设置独占线程 
    protected final void setExclusiveOwnerThread(Thread thread) {
        exclusiveOwnerThread = thread;
    }
    
    // 获取独占线程 
    protected final Thread getExclusiveOwnerThread() {
        return exclusiveOwnerThread;
    }
}

AbstractOwnableSynchronizer抽象类中,可以设置独占资源线程和获取独占资源线程。
分别为setExclusiveOwnerThread与getExclusiveOwnerThread方法,这两个方法会被子类调用。

2.5.2 类的内部类 - Node类

static final class Node {
    // 模式,分为共享与独占
    // 共享模式
    static final Node SHARED = new Node();
    // 独占模式
    static final Node EXCLUSIVE = null;        
    // 结点状态
    // CANCELLED,值为1,表示当前的线程被取消
    // SIGNAL,值为-1,表示当前节点的后继节点包含的线程需要运行,也就是unpark
    // CONDITION,值为-2,表示当前节点在等待condition,也就是在condition队列中
    // PROPAGATE,值为-3,表示当前场景下后续的acquireShared能够得以执行
    // 值为0,表示当前节点在sync队列中,等待着获取锁
    static final int CANCELLED =  1;
    static final int SIGNAL    = -1;
    static final int CONDITION = -2;
    static final int PROPAGATE = -3;        

    // 结点状态
    volatile int waitStatus;        
    // 前驱结点
    volatile Node prev;    
    // 后继结点
    volatile Node next;        
    // 结点所对应的线程
    volatile Thread thread;        
    // 下一个等待者
    Node nextWaiter;
    
    // 结点是否在共享模式下等待
    final boolean isShared() {
        return nextWaiter == SHARED;
    }
    
    // 获取前驱结点,若前驱结点为空,抛出异常
    final Node predecessor() throws NullPointerException {
        // 保存前驱结点
        Node p = prev; 
        if (p == null) // 前驱结点为空,抛出异常
            throw new NullPointerException();
        else // 前驱结点不为空,返回
            return p;
    }
    
    // 无参构造方法
    Node() {    // Used to establish initial head or SHARED marker
    }
    
    // 构造方法
        Node(Thread thread, Node mode) {    // Used by addWaiter
        this.nextWaiter = mode;
        this.thread = thread;
    }
    
    // 构造方法
    Node(Thread thread, int waitStatus) { // Used by Condition
        this.waitStatus = waitStatus;
        this.thread = thread;
    }
}

每个线程被阻塞的线程都会被封装成一个Node结点,放入队列。每个节点包含了一个Thread类型的引用,并且每个节点都存在一个状态,具体状态如下。

  • 值为0,表示当前节点在sync queue中,等待着获取锁。
  • CANCELLED,值为1,表示当前的线程被取消。
  • SIGNAL,值为-1,表示当前节点的后继节点包含的线程需要运行,需要进行unpark操作。
  • CONDITION,值为-2,表示当前节点在等待condition,也就是在condition queue中。
  • PROPAGATE,值为-3,表示当前场景下后续的acquireShared能够得以执行。

2.5.3 类的属性

属性中包含了头结点head,尾结点tail,状态state、自旋时间spinForTimeoutThreshold,还有AbstractQueuedSynchronizer抽象的属性在内存中的偏移地址,通过该偏移地址,可以获取和设置该属性的值,同时还包括一个静态初始化块,用于加载内存偏移地址。

public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer
    implements java.io.Serializable {    
    // 版本号
    private static final long serialVersionUID = 7373984972572414691L;    
    // 头结点
    private transient volatile Node head;    
    // 尾结点
    private transient volatile Node tail;    
    // 状态
    private volatile int state;    
    // 自旋时间
    static final long spinForTimeoutThreshold = 1000L;
    
    // Unsafe类实例
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    // state内存偏移地址
    private static final long stateOffset;
    // head内存偏移地址
    private static final long headOffset;
    // state内存偏移地址
    private static final long tailOffset;
    // tail内存偏移地址
    private static final long waitStatusOffset;
    // next内存偏移地址
    private static final long nextOffset;
    // 静态初始化块
    static {
        try {
            stateOffset = unsafe.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("state"));
            headOffset = unsafe.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("head"));
            tailOffset = unsafe.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
            waitStatusOffset = unsafe.objectFieldOffset
                (Node.class.getDeclaredField("waitStatus"));
            nextOffset = unsafe.objectFieldOffset
                (Node.class.getDeclaredField("next"));

        } catch (Exception ex) { throw new Error(ex); }
    }
}

三、独占模式和共享模式

3.1 AQS独占模式

​ 所谓独占模式,即只允许一个线程获取同步状态,当这个线程还没有释放同步状态时,其他线程是获取不了的,只能加入到同步队列,进行等待。

独占模式两个功能

  • 1、获取同步资源的功能。当多个线程同时竞争同步资源的时候,只有一个线程能获取到同步资源,其他未获取到同步资源的线程必须在当前位置等待。
  • 2、释放同步资源的功能。获取同步资源的线程用完同步资源后,释放这个同步资源,并唤醒正在等待同步资源的一个或多个线程。

3.1.1 独占模式获取资源(acquire方法)

独占模式下,获取资源的使用权主要是通过acquire()方法实现的,acquire()方法代码如下:

/**
* 以独占的方式获取同步资源,此方法不响应中断
*/
public final void acquire(int arg) {
        //1、调用tryAcquire()方法尝试获取同步资源
        //如果tryAcquire()方法返回true,那么acquore()方法执行结束
        //tryAcquire()方法一般在AQS自雷中实现
        //2、如果tryAcquire()方法返回false
        //就调用addWaiter()方法将当前调用acquire()方法的线程入队
        //3、调用acquireQueued()方法在等待队列中获取同步资源
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
}

其执行流程可以分为以下几步:

1. 执行tryAcquire()方法

调用tryAcquire()方法尝试获取同步资源的使用权限。如果获取成功, tryAcquire()方法就会返回true,整个if条件将会返回false,因此acquire()执行结束。如果tryAcquire()方法返回false,就说明当前环境中存在竞争同步资源的线程,因此造成当前的线程获取同步资源的使用权限失败。当tryAcquire()方法返回false时,则执行步骤2。

 protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }

2. 执行addWaiter()方法

/**
 * 将未获取到共享资源的线程添加到队列的尾结点
 *方法参数 Node.EXCLUSIVE 表示当前是独占模式 
 */

private Node addWaiter (Node mode){
    //用当前线程创建一个 Node 结点
    Node node = new Node(Thread.currentThread(),mode);
    // 以下代码的执行逻辑是∶
    // 首先获取原队列的尾结点
    // 将当前结点加入队列尾部,如果入队成功,就返回新结点 node
    // 如果入队失败,就采用自旋加入结点直至入队成功返回该结点 
    Node pred = tail;
    // 如果当前队尾非空 
    if (pred != null){
        //将 node 结点的 prev 引用指向队尾结点 
        node.prev = pred;
        // 如果通过 CAS 入队尾成功
        // 即 node 结点成为新的对尾结点
        if (compareAndSetTail(pred,node)){
            // 原队尾 pred 结点的 next 引用指向 node 
            pred.next = node;
            // 返回 node 
            return node;
        }
    }
    // 如果队尾为空
    //或者通过CAS 进入队尾失败,即当前环境存在多个线程竞争入队
    // 通过 enq()方法自旋 
    enq (node);
    // 返回 node 结点 
    return node;
}

addWaiter()方法通过CAS(Compare And Swap,比较再交换)方式使线程进入队尾。CAS有3个操作数,分别是内存值V、旧的期望值A和将要修改的新值B。当且仅当旧的期望值A和内存值V相等时,将内存值V修改为新值B。在addWaiter()方法中通过CAS方式入队,当且仅当队尾结点没有被修改时,当前结点才可以入队成功,否则执行enq()方法

    /**
     * 自旋方式使 node 结点进入队尾
     */

    private Node enq(final Node node){
        // 自旋
        for (;;){
            // 队尾结点
            Node t = tail;
            // 如果队尾结点为空,即队列为空
            if (t == null){
                // 创建虚拟头结点
                // 通过 CAS 设置队列头结点
                if (compareAndSetHead(new Node()))
                    // 此时队列只有一个结点
                    // 即头结点等于尾结点
                    tail = head;
            } else (
               // 如果队列尾结点非空
               // 设置node 结点的 prev 引用指向t 
               node.prev = t;
               // 通过CAS 设置新的队尾结点为 node 结点
               if (compareAndSetTail(t,node)){
                  // 原队尾结点的 next 引用指向 node
                  // node 结点就是新的尾结点 tail
                  t.next = node;
                  // 自旋结束,返回原尾结点
                  return t;
            }
        }
    }
}

3. 执行acquireQueued()方法

当线程进入同步队列后,会在同步队列中等待同步资源的释放。当线程争取到同步资源的使用权后,线程将会从同步队列中出队。

final boolean acquireQueued(final Node node, int arg) {
  //是否已获取锁的标志,默认为true 即为尚未
  boolean failed = true;
  try {
      //等待中是否被中断过的标记
      boolean interrupted = false;
      for (;;) {
          //获取前节点
          final Node p = node.predecessor();
          //如果当前节点已经成为头结点,尝试获取锁(tryAcquire)成功,然后返回
          if (p == head && tryAcquire(arg)) {
              setHead(node);
              p.next = null; // help GC
              failed = false;
              return interrupted;
          }
          //shouldParkAfterFailedAcquire根据对当前节点的前一个节点的状态进行判断,对当前节点做出不同的操作
          //parkAndCheckInterrupt让线程进入等待状态,并检查当前线程是否被可以被中断
          if (shouldParkAfterFailedAcquire(p, node) &&
              parkAndCheckInterrupt())
              interrupted = true;
      }
  } finally {
      //将当前节点设置为取消状态;取消状态设置为1
      if (failed)
          cancelAcquire(node);
  }
}
  • 3.1 acquireQueued()方法调用predecessor()方法获取结点的前驱结点。
/**
 * 获取当前结点的前驱结点,会抛出 Nul1PointerException 异常
 */
final Node predecessor()throws NullPointerException {
    // 指向当前结点前驱结点的引用 
    Node p = prev;
    // 如果前驱结点为空,就抛出 NullPointerException 
    if (p == null)
        throw new NullPointerException();
    else
        // 返回前驱结点 
        return p;
}
  • 3.2 acquireQueued()调用predecessor()方法获取当前结点的前驱结点后,如果当前结点的前驱结点为头结点,就调用tryAcquire()方法尝试获取资源。如果tryAcquire()方法返回true,即获取资源成功,就通过setHead()方法将当前结点设置为头结点。
private void setHead(Node node) {
    head = rnode;
    node.thread = null;
    node.prev = null;
}
  • 3.3 acquireQueued()调用shouldParkAfterFailedAcquire()方法
// 当获取(资源)失败后,检查并且更新结点状态
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    // 获取前驱结点的状态
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL) // 状态为SIGNAL,为-1
        /*
            * This node has already set status asking a release
            * to signal it, so it can safely park.
            */
        // 可以进行park操作
        return true; 
    if (ws > 0) { // 表示状态为CANCELLED,为1
        /*
            * Predecessor was cancelled. Skip over predecessors and
            * indicate retry.
            */
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0); // 找到pred结点前面最近的一个状态不为CANCELLED的结点
        // 赋值pred结点的next域
        pred.next = node; 
    } else { // 为PROPAGATE -3 或者是0 表示无状态,(为CONDITION -2时,表示此节点在condition queue中) 
        /*
            * waitStatus must be 0 or PROPAGATE.  Indicate that we
            * need a signal, but don't park yet.  Caller will need to
            * retry to make sure it cannot acquire before parking.
            */
        // 比较并设置前驱结点的状态为SIGNAL
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL); 
    }
    // 不能进行park操作
    return false;
}
  • 3.4 shouldParkAfterFailedAcquire() 方法返回false,就会造成线程进入 acquireQueued() 方法自旋;如果 shouldParkAfterFailedAcquire() 方法返回true,就会执行 parkAndCheckInterrupt() 方法。
// 进行park操作并且返回该线程是否被中断
private final boolean parkAndCheckInterrupt() {
    // 在许可可用之前禁用当前线程,并且设置了blocker
    LockSupport.park(this);
    return Thread.interrupted(); // 当前线程是否已被中断,并清除中断标记位
}

4. 执行selfInterrupt()方法

如果线程在期间发生中断,就维护线程中断的状态,但并不响应中断。

/**
 * 设置线程中断,并不对中断做出响应
 */
  static void selfInterrupt() {
      Thread.currentThread().interrupt();
  }

独占模式获取资源的执行流程:

9.png

3.1.2 独占模式释放资源(release方法)

独占模式下,释放资源的使用权是通过release()方法实现的,release()方法代码如下:

public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
}

其执行流程可以分为以下几步:

1. 执行tryRelease()方法

调用tryRelease()方法尝试释放同步资源的使用权限。若释放成功,则tryRelease()方法将会返回true,然后执行步骤2。若tryRelease()方法返回false,则释放资源失败。

 protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
    }

2. 执行unparkSuccessor()方法

unparkSuccessor()方法唤醒等待队列中的后继结点,使之可以再次竞争同步资源。

/**
 * 唤醒同步队列的后继结点
 */
private void unparkSuccessor(Node node) {
    /*结点的状态
     * 如果小于零,则肯定不是 CANCELLED状态
     */
    int ws = node.waitStatus;
    if (ws < 0)
        //通过 CAS 方式将结点状态修改为 0
        compareAndSetWaitStatus(node, ws, 0);
    /*
     * node 结点的后继结点
     */
    Node s = node.next;
    //如果后继结点非空,且状态大于0,即 CANCELLED状态
    //说明后继结点对应的线程取消对资源的等待 
    if (s == null II s.waitStatus > 0){
        // 将后继结点置为空
        s = null;
        // 从同步队列尾结点开始向前遍历,
        // 找到同步队列中 node 结点后第一个等待唤醒的结点
        // 如果遍历到的结点 t 非空且不等于当前结点 node,
        // 则校验结点 t 的状态
        for (Node t = tail; t != null && t != node; t = t.prev)
            // 如果结点的状态小于等于0 
            if (t.waitStatus <= 0)
                // 则将 s 指向 t 
                s = t;
    }
    // 如果结点s 非空 
    if (s != null)
        // 唤醒结点s 对应的线程
        LockSupport.unpark(s.thread);
}

unparkSuccessor()方法执行流程如下:

(1)将node结点的状态设置为0。

(2)寻找到下一个非取消状态的结点s。

(3)如果结点s不为null,则调用LockSupport.unpark(s.thread)方法唤醒结点s对应的线程。

(4)unparkSuccessor()方法唤醒线程的顺序即线程添加到同步队列的顺序。

3.2 AQS共享模式

3.2.1 共享模式获取资源(acquireShared方法)

在共享模式下,获取资源的使用权主要是通过acquireShared()方法实现的。acquireShared()方法代码如下:

public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

其执行流程可以分为以下几步:

1. 执行tryAcquireShared()方法

AbstractQueuedSynchronizer类并未实现tryAcquireShared()方法,而是交由其子类实现。如果tryAcquireShared()方法返回小于0,即线程获取共享资源失败,就通过doAcquireShared()方法使线程进入同步队列。

  • 负值代表获取失败;
  • 0代表获取成功,但没有剩余资源;
  • 正数表示获取成功,还有剩余资源,其他线程还可以去获取。
// 共享模式下尝试获取资源,此方法需要由子类覆盖 
protected int tryAcquireShared(int arg) { 
    throw new UnsupportedOperationException(); 
 }

2. 执行doAcquireShared()方法

共享模式调用的是addWaiter(Node.SHARED)方法将线程加入队列的尾部,表明该结点处于共享模式。获取资源的线程调用setHeadAndPropagate(node, r)方法。

private void doAcquireShared(int arg) {
    //加入队列尾部
    final Node node = addWaiter(Node.SHARED);
    //是否成功标志
    boolean failed = true;
    try {
        //等待过程中是否被中断过的标志
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();//获取前驱节点
            if (p == head) {//如果到head的下一个,因为head是拿到资源的线程,此时node被唤醒,很可能是head用完资源来唤醒自己的
                int r = tryAcquireShared(arg);//尝试获取资源
                if (r >= 0) {//成功
                    setHeadAndPropagate(node, r);//将head指向自己,还有剩余资源可以再唤醒之后的线程
                    p.next = null; // help GC
                    if (interrupted)//如果等待过程中被打断过,此时将中断补上。
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            
            //判断状态,队列寻找一个适合位置,进入waiting状态,等着被unpark()或interrupt()
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }   
}

3.2.2 共享模式释放资源( releaseShared方法)

共享模式下,释放资源的使用权是通过releaseShared()方法实现的,releaseShared()方法代码如下:

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

1. 执行tryReleaseShared()方法

tryReleaseShared()方法执行成功,就会执行doReleaseShared()方法唤醒后继结点。

 protected boolean tryReleaseShared(int arg) {
        throw new UnsupportedOperationException();
    }

2. 执行doReleaseShared()方法

唤醒后继节点线程

  • 当state为正数,去获取剩余共享资源;
  • 当state=0时去获取共享资源。
private void doReleaseShared() {
        /*
         * fails, if so rechecking.
         */
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {// 这个head!=tail 说明阻塞队列中至少2个节点 不然也没必要去传播唤醒 如果就自己一个节点 就算资源条件满足 还换个谁呢?
                int ws = h.waitStatus;// head 节点状态SIGNAL
                if (ws == Node.SIGNAL) {// 如果head状态是
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h);//是和独占锁释放用的同样的方法 唤醒的是下一个节点 上一篇有分析到
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;          //这边设置为-3 是为了唤醒的传播 也就是满足上一个方法有判断waitStatus 小于0
            }
            if (h == head)   
                break;
        }
    }

四 参考资料

Doug Lea:《Java并发编程实战》

方腾飞:《Java并发编程的艺术》

带你看看Java-AQS同步器 源码解读<一>独占锁加锁