JAVA并发基石之AQS --- 心法篇

语言: CN / TW / HK

本文已参与「新人创作礼」活动,一起开启掘金创作之路。


一、起源:

1、AQS是什么?

全称 AbstractQueuedSynchronizer 抽象队列同步器,是java.util.concurrent.locks 包下的基础组件, 是用于构建锁和同步器的框架。

简单的理解就是多线程环境下,AQS 封装了一揽子对临界资源线程安全的操作,以 ReentrantLock 来看: 获取锁?成功则锁住,否则放入 FIFO 队列等待获取锁(线程挂起或者说阻塞),锁的释放等这些底层操作都由 AQS 封装;

许多同步器都可以通过 AQS 很容易并且高效的构造出来。

2、AQS在Java并发编程中扮演什么角色?

java.util.concurrent 包下许多可阻塞类的底层实现,如 ReentrantLock, CountDownLatch, Semaphore, ReentrantReadWriteLock, SynchronousQueue 和 FutureTask 都是基于 AQS 构建。

可见搞懂 AQS 在阅读 java 并发包下的其他组件时是多么必要!!!

3、AQS核心是什么?

包含三部分核心内容,状态队列、以及由具体工具类去实现的获取/释放等方法

java /** * The synchronization state. */ private volatile int state;

1)state状态:

如上定义, 它的含义也会由具体的实现类不同而具有不同的含义,来看看几个常用的同步器中的 state 含义: - ReentrantLock:state 状态用于表示重入次数,重入1次 state 加1,释放一次 state 减 1,state 为零表示没有任何线程占用锁 - Semaphore 同步器:state 状态用于保存当前可用许可的数量,当尝试拿一个许可并成功拿到时,相应的许可数量减去; 如果尝试释放并成功释放许可时,许可数量将会增加 - CountDownLatch 同步器:和 Semaphore 和相似,state 状态保存的是当前的计数值,countDown 方法调用 release,从而导致计数值递减, 并且当计数值为零时,解除所有线程的阻塞;await 调用 acquire,当计数器为零时,acquire 将立即返回,否则将阻塞 - FutureTask:state 状态保存任务的状态,如正在运行、已完成或已取消 - ReentrantReadWriteLock:此实现有两把锁分别是读锁和写锁,state 状态使用 16 位的状态来表示写入锁的计数,使用另外 16 位的状态来表示读取锁的计数

2)FIFO队列 先进先出队列: - 主要的作用是用来存储等待的线程。比如很多线程都想要去抢夺这把锁,但是大部分都是抢不到的,该如何处理这些线程呢?那就将它们放进队列里面,然后进行处理 - 当多个线程去竞争同一把锁的时候,就需要排队机制把那些没能拿到锁的线程串联起来,当前面的线程释放锁之后,这个管理器就会挑选一个合适的线程来尝试抢刚才被释放的那把锁。所以AQS就一直维护这个队列,并把等待的线程都放进队列里面。 - 队列内部是双向链表,结构看起来简单,但是要线程安全的维护这个队列,也着实不容易....

3)获取/释放方法: - AQS 使用模版方法模式,将 tryAcquire,tryRelease 等具体获取和释放资源的方法提供给子类实现,这里的资源主要针对的是state值的处理,来看一个例子就明白了:

```java /* *

Here is a non-reentrant mutual exclusion lock class that uses * the value zero to represent the unlocked state, and one to * represent the locked state. While a non-reentrant lock * does not strictly require recording of the current owner * thread, this class does so anyway to make usage easier to monitor. * It also supports conditions and exposes / class Mutex implements Lock, java.io.Serializable {

     // Our internal helper class
     // 这里去继承AQS并实现相关方法
     private static class Sync extends AbstractQueuedSynchronizer {
         // Reports whether in locked state
         @Override
         protected boolean isHeldExclusively() {
             return getState() == 1;
         }

         // 使用者实现
         // Acquires the lock if state is zero
         @Override
         public boolean tryAcquire(int acquires) {
             assert acquires == 1; // Otherwise unused
             if (compareAndSetState(0, 1)) {
                 setExclusiveOwnerThread(Thread.currentThread());
                 return true;
             }
             return false;
         }

         // 使用者实现
         // Releases the lock by setting state to zero
         @Override
         protected boolean tryRelease(int releases) {
             assert releases == 1; // Otherwise unused
             if (getState() == 0) {
                 throw new IllegalMonitorStateException();
             }
             setExclusiveOwnerThread(null);
             setState(0);
             return true;
         }

         // Provides a Condition
         Condition newCondition() { return new ConditionObject(); }

         // Deserializes properly
         private void readObject(ObjectInputStream s)
                 throws IOException, ClassNotFoundException {
             s.defaultReadObject();
             setState(0); // reset to unlocked state
         }
     }

     // The sync object does all the hard work. We just forward to it.
     private final Sync sync = new Sync();

     @Override
     public void lock()                { sync.acquire(1); }
     @Override
     public boolean tryLock()          { return sync.tryAcquire(1); }
     @Override
     public void unlock()              { sync.release(1); }
     @Override
     public Condition newCondition()   { return sync.newCondition(); }
     public boolean isLocked()         { return sync.isHeldExclusively(); }
     public boolean hasQueuedThreads() { return sync.hasQueuedThreads(); }
     @Override
     public void lockInterruptibly() throws InterruptedException {
         sync.acquireInterruptibly(1);
     }
     @Override
     public boolean tryLock(long timeout, TimeUnit unit)
             throws InterruptedException {
         return sync.tryAcquireNanos(1, unit.toNanos(timeout));
     }

 }
 //

```

上面通过继承 AbstractQueuedSynchronizer 实现的非重入独占锁,state=0 表示可以获取锁,state=1 表示次锁已经被持有了, 可以看见通过重写 tryAcquire,tryRelease 方法对具体资源的处理。

4、AQS核心原理

  • state:状态量 也就是资源,实际获取、释放都是通过围绕 state 状态量判断
     private volatile int state;
    
    • 等待队列:实际是通过双向链表实现的队列,分别有队头引用 head, 队尾引用 tail
    • head: 特别需要注意的是, head 对应的线程已经是获得了资源,因此我们常说的唤醒是针对的除 head 节点之外的其他节点
    • 等待队列:强调上一条,真正等待唤醒的节点是除 head 之外的其他节点
    • 等待队列中被唤醒的节点只有获取资源成功了才能设置成 head
    • 同时需要注意的是,只有前驱节点是 head 的节点才有机会尝试获取资源

java final Node p = node.predecessor(); if (p == head && tryAcquire(arg))

  • AQS大致的思路:定义 state 资源,作为能否获取成功的依据,如果获取失败(那也得想办法处理不是?),就放进 AQS 维护的一个等待队列, 等待资源释放了,就尝试唤醒后继节点(一般是这样,但如果是 cancel 的需要排除队列),被唤醒的节点尝试去获取资源,这个时候也可能会失败的哈, 比如 ReentrantLock 的非公平锁实现中,每次 tryAcquire 都要尝试去获取资源(这里就和等待队列里被唤醒的节点是竞争关系),通过 CAS+自旋的方式保证线程安全性

  • Node节点定义:

```java static final class Node { // 表示当前节点在共享模式上等待 static final Node SHARED = new Node(); // 表示当前节点在独占模式上等待 static final Node EXCLUSIVE = null;

         // waitStatus有5个值,初始值=0
         // CANCELLED =  1
         // SIGNAL    = -1
         // CONDITION = -2
         // PROPAGATE = -3
         volatile int waitStatus;

         volatile Node prev; // 前驱节点

         volatile Node next; // 后继节点

         volatile Thread thread; // 此节点对应的线程

         Node nextWaiter;

         ....

```

其中: - SIGNAL = -1,表明后继节点需要被当前节点唤醒 - CANCELLED = 1,由于超时或中断,该节点被取消,节点永远不会离开此状态。特别的是,具有取消节点的线程永远不会再次阻塞 - CONDITION = -2,表示结点线程等待在 condition 上,当其他线程调用了 Condition 的 signal() 方法后,CONDITION 状态的结点将从等待队列转移到同步队列中,等待获取同步锁 - PROPAGATE = -3,共享模式下,前驱节点不仅会唤醒其后继节点,同时也可能唤醒后继节点的后继节点
- head,tail 节点:分别是队头,队尾节点

```java /* * Head of the wait queue, lazily initialized. Except for * initialization, it is modified only via method setHead. Note: * If head exists, its waitStatus is guaranteed not to be * CANCELLED. / // 等待队列头节点,只能通过方法setHead设置head,如果head节点存在, // 它的waitStatus状态一定不会是CANCELLED private transient volatile Node head;

     /**
      * Tail of the wait queue, lazily initialized.  Modified only via
      * method enq to add new wait node.
      */
     // 等待队列尾节点,只会在enq方法中添加到队列
     private transient volatile Node tail;

```

二、源码分析

独占模式

1、acquire方法

忽略中断的独占模式的入口方法 - 所谓忽略中断,就是当线程发生中断的情况仅仅只是记录中断状态,而不会类似于抛出中断异常来响应,具体响应中断可由实现类处理

java // arg参数表示的需要获取的资源量 public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }

主要操作: - 首先通过 tryAcquire 尝试获取资源,如果获取成功这里就结束了;tryAcquire方式使用的模版方法模式来设计,由实现类去实现具体如何获取资源这个操作 - 如果获取失败了,通过a ddWaiter 方法把当前线程封装成一个 Node 节点放进等待队列队尾 - acquireQueued 核心方法,通常等待队列里的节点在尝试获取锁失败之后,都会阻塞在此放方法中(park 操作,也就是线程挂起), 当节点被唤醒时,又从被挂起的地方开始尝试获取资源;因为在被阻塞的过程中,此线程可能被中断了,所以需要记录中断标志(不立即响应),交给上层处理,acquireQueued 返回值就代表释放已经中断 - selfInterrupt 就是检测到中断后,设置中断标志

```java // 此操作会清除中断标志 Thread.interrupted()

    // 所以这里需要重新设置中断标志
    static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }

```

1.1、addWaiter

简单来说就是将当前线程封装成 Node 节点,添加至队列尾部

```java /* * Creates and enqueues node for current thread and given mode. * * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared * @return the new node / private Node addWaiter(Node mode) { Node node = new Node(Thread.currentThread(), mode); // Try the fast path of enq; backup to full enq on failure Node pred = tail; // 如果tail为空说明队列还没有初始化,需要通过enq进行处理 if (pred != null) { node.prev = pred; // 如果tail节点设置成功了就直接返回,否则通过enq处理(毕竟多线程环境CAS操作也有可能失败嘛) if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } enq(node); return node; }

```

1.1.1 enq方法

自循环的方式将 node 加入队列尾部,直到成功

java // 返回当前节点node的前驱节点 private Node enq(final Node node) { for (;;) { Node t = tail; // 如果还没有tail节点,说明队列还没有初始化 if (t == null) { // Must initialize // 注意这里先加入的是一个空Node节点,同时也没有直接退出enq方法 // 也就是上面说的,除head之外 等待队列里的其他节点才是真正在阻塞状态的节点 if (compareAndSetHead(new Node())) tail = head; } else { node.prev = t; // CAS设置成了就直接返回 if (compareAndSetTail(t, node)) { t.next = node; return t; } } } }

这一通过自旋的方式保证能够将 node 节点添加至等待队列尾部

1.2、acquireQueued

独占不响应中断模式尝试获取资源,如果此节点 node 的前驱节点是 head,那它才有资格去尝试获取资源,获取失败之后挂起线程,进入阻塞状态

```java final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { // 记录中断状态,用做方法返回 boolean interrupted = false; for (;;) { final Node p = node.predecessor(); // 前驱节点 // 如果是前驱是head节点才去尝试获取资源 if (p == head && tryAcquire(arg)) { // 只有获取资源成功之后才重新设置head为当前节点node setHead(node); p.next = null; // help GC failed = false; return interrupted; } // 判断获取资源失败之后应该挂起线程 if (shouldParkAfterFailedAcquire(p, node) && // 挂起线程,并在被唤醒之后检查线程中断状态 parkAndCheckInterrupt()) interrupted = true; } } finally { // 如果此节点node在尝试获取资源的过程中出现了异常,那就直接从等待队列里面取消此节点 if (failed) cancelAcquire(node); } }
//

```

主要操作: - 如果当前 node 节点的前驱是 head 节点,那么才有资格去尝试获取资源 - 如果获取资源成功之后,才会设置 head 为当前节点 node - 在获取资源失败之后一般就会考虑是否需要挂起线程 - 挂起的线程在被唤醒之后就会继续从挂起前的地方开始操作,这个时候需要检查一下中断标志,如果中断了需要记录一下,方便返回

1.2.1、shouldParkAfterFailedAcquire

判断是否应该挂起当获取资源失败之后

```java private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; // 这里是拿到的是前驱节点的状态,当前节点node需要确保前驱节点是SIGNAL状态,因为只有这样 // 前驱节点在释放资源之后才会唤醒当前节点node,也就是当前节点才能放心的去挂起 if (ws == Node.SIGNAL) / * This node has already set status asking a release * to signal it, so it can safely park. / return true; if (ws > 0) { // 如果前驱节点被取消了,那不断尝试往前找未取消的前驱节点 // 这是一定能找到的,因为至少head节点是不可能被取消的 / * Predecessor was cancelled. Skip over predecessors and * indicate retry. / do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { / * 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. / // 这里并不会直接挂起当前节点,而是设置前驱节点的waitStatus为SIGNAL,表明在它释放资源之后需要唤醒当前节点 // 为啥不直接挂起当前节点node呢?采用的做法是在线程挂起之前再尝试一次获取资源,如果成功就不需要挂起了 // 考虑这样一个场景:队列里有一个head和一个刚加入队列的节点a,节点a尝试过一次获取资源,但是失败了,因为head节点还没有释放资源 // 在节点a判断是否需要挂起之前,这个时候正好head节点释放资源,这个时候节点a只要再尝试一次获取资源也就可以成功了 // 毕竟挂起和唤醒线程的消耗是高于自旋等待的消耗 compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; } //

```

主要操作 - 当前节点node需要确保前驱节点是SIGNAL状态后 它才放心把自己挂起(因为会有人唤醒自己嘛) - 需要注意的是:waitStatus大于0表示节点是取消状态,只有小于等于0才是有效状态 - 当前驱节点的ws大于0了说明前驱节点被取消了,需要为当前节点找到一个未被取消的前驱节点,这个前驱节点一定是能找到的,至少head节点是不可能被取消的 - 否则的话需要将前驱节点的状态设置为SIGNAL,表明前驱节点释放资源后需要通知到自己(也就是当前node节点);当前节点node再经过一轮尝试获取资源失败后就可以直接挂起了

1.2.2、parkAndCheckInterrupt

挂起当前线程,并检查中断标志

```java private final boolean parkAndCheckInterrupt() { LockSupport.park(this); return Thread.interrupted(); }

```

借助LockSupport#park将当前线程挂起,同时通过Thread.interrupted()检查当前线程的中断标志,需要注意的是, 此方法会清除中断标志,因此如果需要保存中断状态的话,在上层需要再重新设置

1.2.3、cancelAcquire

取消需要获取资源的node

```java private void cancelAcquire(Node node) { // Ignore if node doesn't exist if (node == null) return;

    node.thread = null;

    // Skip cancelled predecessors
    Node pred = node.prev;
    // 找到未被取消的前驱节点
    while (pred.waitStatus > 0)
        node.prev = pred = pred.prev;

    // predNext is the apparent node to unsplice. CASes below will
    // fail if not, in which case, we lost race vs another cancel
    // or signal, so no further action is necessary.
    Node predNext = pred.next;

    // Can use unconditional write instead of CAS here.
    // After this atomic step, other Nodes can skip past us.
    // Before, we are free of interference from other threads.
    node.waitStatus = Node.CANCELLED;

    // If we are the tail, remove ourselves.
    // 如果当前节点node为tail,那就重新将pred设置为tail
    if (node == tail && compareAndSetTail(node, pred)) {
        // 设置成功之后,因为pred为tail节点,肯定是没有后继节点了
        compareAndSetNext(pred, predNext, null);
    } else {
        // If successor needs signal, try to set pred's next-link
        // so it will get one. Otherwise wake it up to propagate.
        int ws;
        if (pred != head &&
            ((ws = pred.waitStatus) == Node.SIGNAL ||
             (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
            pred.thread != null) {
            Node next = node.next;
            if (next != null && next.waitStatus <= 0)
                // 相当于将node节点从队列里面排除了
                compareAndSetNext(pred, predNext, next);
        } else {
            // 试想当前node节点是等待队列的第一个节点(head除外),如果它被取消了,是不是得担负起唤醒后继节点的责任?
            // 假如节点node是head节点释放资源后唤醒的一个节点,而节点node在获取资源的时候发生了异常,node节点需要被取消
            // 但是传播唤醒这个责任是不是就落到了node节点的头上? (毕竟是head唤醒了你,而你又要跑路,你是不是得先把唤醒责任处理好了再跑呢?)
            unparkSuccessor(node);
        }

        node.next = node; // help GC
    }
}
//

```

主要操作: - 什么时候会被取消?从源码注释来看,当超时或者响应中断的时候会进行取消 - 一般情况下将取消的节点从链表中移除即可 - 如果当前node节点的前驱节点是head,自己(node)本来是被唤醒去获取资源的,现在被取消要删除了,后面的 "兄弟"谁去唤醒呢? 因此这种情况下,我(node)需要唤醒一个后面的"兄弟",才能安静的离去.....

2、release

独占模式下的资源释放

```java public final boolean release(int arg) { // 通过tryRelease去尝试释放资源 if (tryRelease(arg)) { Node h = head; // head不为空说明等待队列里面有待唤醒的节点,waitStatus=0表示后继节点不需要唤醒, // 因此需要满足h.waitStatus!=0才能去唤醒后继节点 if (h != null && h.waitStatus != 0) // 尝试唤醒后继节点 unparkSuccessor(h); return true; } return false; }

```

主要操作: - 通过tryRelease尝试去释放资源,这是利用模版方式模式,具体由子类去实现 - 满足待唤醒条件后,通过unparkSuccessor去唤醒

2.1、unparkSuccessor

唤醒后继节点 ```java private void unparkSuccessor(Node node) { / * If status is negative (i.e., possibly needing signal) try * to clear in anticipation of signalling. It is OK if this * fails or if status is changed by waiting thread. / int ws = node.waitStatus; // 如果ws < 0表明是正常状态,置为0表示不需要唤醒后继节点这类操作 if (ws < 0) compareAndSetWaitStatus(node, ws, 0);

    /*
     * Thread to unpark is held in successor, which is normally
     * just the next node.  But if cancelled or apparently null,
     * traverse backwards from tail to find the actual
     * non-cancelled successor.
     */
    // ws大于0表示已经取消了,那就找到第一个未取消的节点来唤醒,不过这里是通过从后往前扫的方式处理的
    // 通常情况下是唤醒node.next节点,但是考虑到节点被取消或者是null的情况,需要从后面往前扫
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    // 唤醒线程
    if (s != null)
        LockSupport.unpark(s.thread);
}

```

共享模式

此模式下需要搞清楚tryAcquireShared方法返回的三类数值 - 如果返回小于0的值:表示此次获取资源失败 - 如果返回等于0的值:表示此次获取资源成功,但是资源已经用尽了,不能向后传播唤醒操作了(因为没资源可用的,只能等到释放之后) - 如果返回大于0的值:表示此次获取资源成功,同时下一次也是有可能成功获取资源,唤醒操作向后传播(毕竟有资源可用嘛)

需要注意的是:获取资源成功之后会进行的setHead操作,也就是head会被改变,但并不是说之前head对应的节点已经释放资源结束了,换句话说新head节点获取资源运行的同时,旧head节点仍然可能在运作中,其携带的资源并未释放

举一个实际例子来看看: - 假设资源总量为10,现在有4个线程a,b,c,d想要获取资源运行,需要的资源分别是5,3,3,4 - 这个时候假设a,b已经抢占资源并运行了,占用资源数为8,剩余资源2;需要注意的是线程a,b可不是等待队列的节点哈 - 此时线程c来了,想要获取资源,但是 3 > 2,获取失败,进入等到队列 - 线程d也来了,同样4 > 2获取失败,进入等待队列 - 此时等待队列的状态:两个节点等待唤醒,head节点中的Node是通过new Node()方式的"假节点",也就是没有实际运行的线程 - 这个时候线程a运行结束,释放资源数量为5,此时资源剩余总量为7,尝试唤醒线程c(优先队列,先唤醒最开始的嘛), 线程c唤醒后成功获取资源,并设置head为线程c所在节点,此时资源剩余量为4;需要注意的是这里head已经变了(这里是关键), 说明前一个被唤醒的节点已经成功获取了资源,那么很有可能还有可用资源,OK,那根据共享模式的传播特性,再去唤醒下一个共享模式的节点, 因此线程d对应的节点也被唤醒了,并成功获取资源;这个时候在运行中的线程有b,c,d

从这个例子可以看出,它并不是盲目唤醒后继节点,而是根据head变化来判断上一个唤醒的节点是否已经成功获取了资源,如果是则说明还有资源可用 否则的话,认为没有资源可用,不在唤醒后继节点了(这里还有一个问题,如果可用资源是4,等待队列中的节点a,b所需资源分别是5,4,但由于顺序关系 节点a被唤醒,尝试获取资源失败,此时尽管b节点所需资源满足要求,但是不会唤醒b节点,传播终止...)

1、acquireShared

共享模式下获取资源

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

```

  • 通过tryAcquireShared方法尝试获取资源,也是模版方法,由子类具体实现
  • 如果获取失败,就尝试放进等待队列

1.1 doAcquireShared

获取资源

```java /* * Acquires in shared uninterruptible mode. * @param arg the acquire argument / private void doAcquireShared(int arg) { // 将当前线程加入队列尾部 final Node node = addWaiter(Node.SHARED); boolean failed = true; try { boolean interrupted = false; for (;;) { // 前驱节点 final Node p = node.predecessor(); // 同样,只有前驱节点是head的节点才有机会去获取资源 if (p == head) { // 模版方法,具体资源获取由子类实现 int r = tryAcquireShared(arg); // 如果获取资源成功 if (r >= 0) { // 设置head为当前节点,同时可能的话尝试唤醒后继共享模式的节点 // 这里r表示的资源剩余量,只有剩余量大于0才会去尝试唤醒后继节点 setHeadAndPropagate(node, r); p.next = null; // help GC if (interrupted) selfInterrupt(); failed = false; return; } } // 获取失败后是否需要挂起 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) // 挂起线程,同时在恢复之后检查中断标志 interrupted = true; } } finally { // 如果失败就了取消这个节点 if (failed) cancelAcquire(node); } }

```

看起来和独占模式的获取资源很像吧,有一点区别在于setHeadAndPropagate - 独占模式下,获取资源成功之后通过setHead改变head就可以了 - 在共享模式下,通过setHeadAndPropagate方法更改head, 同时 如果资源剩余量r>0说明有资源可用,尝试唤醒后继共享模式下的节点去获取资源 相比独占模式多了一个唤醒传播机制

1.1.1 setHeadAndPropagate

设置head并尝试唤醒后继节点

```java // 这里参数propagate就是tryAcquireShared方法的当前资源剩余量 private void setHeadAndPropagate(Node node, int propagate) { Node h = head; // Record old head for check below setHead(node);

    // propagate > 0 说明有资源可用
    if (propagate > 0 || h == null || h.waitStatus < 0 ||
        (h = head) == null || h.waitStatus < 0) {
        Node s = node.next;
        if (s == null || s.isShared())
            // 尝试唤醒后继节点
            doReleaseShared();
    }
}

```

1.1.2 doReleaseShared

共享模式下,唤醒后继节点以确保传播性;而独占模式的仅在资源释放时,才会去获取head节点的后继节点

```java private void doReleaseShared() { // 这里使用自循环是保证在CAS失败之后也要进行重试 for (;;) { Node h = head; if (h != null && h != tail) { int ws = h.waitStatus; // 如果后继节点需要唤醒 if (ws == Node.SIGNAL) { if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) continue; // loop to recheck cases unparkSuccessor(h); } // 后继节点不需要唤醒的话,将h的ws设置为PROPAGATE,以确保release之后的传播性 else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) continue; // loop on failed CAS }

        // 这里很关键,如果head改变了就意味着上一个被唤醒的节点已经成功获得了资源
        // 也就是也许还有资源可以获取,因此尝试唤醒后继节点,以此类推...
        if (h == head)                   // loop if head changed
            break;
    }
}        
//

```

需要关注两点: - 使用自循环是保证在CAS失败之后也要进行重试 - 通过head来判断被唤醒的节点是否已经获取资源(毕竟获取资源成功之后就会改变head的值); 如果head改变了就意味着上一个被唤醒的节点已经成功获得了资源, 也就是也许还有资源可以获取,因此尝试唤醒后继节点,以此类推...

2、releaseShared 释放资源

```java public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { // 这里也是通过doReleaseShared来释放资源 doReleaseShared(); return true; } return false; }

```

三、小结

从源码层面来看分析了独占模式共享模式的获取、释放资源相关源码,其中还有其配套的响应中断的独占模式 和 响应中断的共享模式, 结构上基本一致,只不过在对遇到中断的情况下做了中断响应,这里不在赘述,有兴趣可以看看源码。

整个AQS的结构都是围绕着三个核心点展开 - state状态: 不同的实现类有不同的含义,通俗的理解就是资源 - FIFO队列: 当尝试获取资源失败之后,得想一个办法把失败的线程给存起来,这里也就是给放进队列里了;麻烦的是需要考虑在多线程环境下维护队列的线程安全性,同时需要剔除取消的节点,考虑如何唤醒后继节点等... - 获取/释放资源方法: 因为不同的实现类对资源的定义不同,因此具体的获取和释放资源的方法 通过模版方法的方式交给子类来实现

AQS在具体的工作: - FIFO 是其核心,维护队列,线程挂起、唤醒,取消节点移除、共享模式下的传播性维护等等,都是为了队列节点合理的获取资源,维持其正确性和高效性,保证在多线程环境下资源合理的竞争,竞争者被合理的分配去获取资源。