AbstractQueuedSynchronizer 抽象队列同步器,简称 AQS 。是在 JUC 包下面一个非常重要的基础组件,JUC 包下面的并发锁 ReentrantLock CountDownLatch 等都是基于 AQS 实现的。所以想进一步研究锁的底层原理,非常有必要先了解 AQS 的原理。

介绍

先看下 AQS 的类图结构,以及源码注释,有一定的大概了解之后再从源码入手,一步一步研究它的底层原理。
AQS - 图1
“ 源码注释
提供了实现阻塞锁和相关同步器依靠先入先出(FIFO)等待队列(信号量,事件等)的框架。 此类中设计了一个对大多数基于 AQS 的同步器有用的原子变量来表示状态(state)。 子类必须定义 protected 方法来修改这个 state,并且定义 state 值在对象中的具体含义是 acquired 或 released。 考虑到这些,在这个类中的其他方法可以实现所有排队和阻塞机制。 子类可以保持其他状态字段,但只能使用方法 getState 、setState 和 compareAndSetState 以原子方式更新 state 。
子类应被定义为用于实现其封闭类的同步性能的非公共内部辅助类。 类AbstractQueuedSynchronizer没有实现任何同步接口。 相反,它定义了一些方法,如 acquireInterruptibly 可以通过具体的锁和相关同步器来调用适当履行其公共方法。
此类支持独占模式和共享模式。 在独占模式下,其他线程不能获取成功,共享模式下可以(但不一定)获取成功。 此类不“理解”,在机械意义上这些不同的是,当共享模式获取成功,则下一个等待的线程(如果存在)也必须确定它是否能够获取。 线程在不同模式下的等待共享相同的FIFO队列。 通常情况下,实现子类只支持其中一种模式,但同时使用两种模式也可以,例如ReadWriteLock 。 仅共享模式不需要定义支持未使用的模式的方法的子类。
这个类中定义了嵌套类 AbstractQueuedSynchronizer.ConditionObject ,可用于作为一个 Condition 由子类实现,并使用 isHeldExclusively 方法说明当前线程是否以独占方式进行,release()、 getState() acquire() 方法用于操作 state 原子变量。
此类提供检查和监视内部队列的方法,以及类似方法的条件对象。 根据需要进使用以用于它们的同步机制。
要使用这个类用作同步的基础上,需要重新定义以下方法,如使用,通过检查和或修改 getState 、setState 或 compareAndSetState 方法:
tryAcquire
tryRelease
tryAcquireShared
tryReleaseShared
isHeldExclusively

通过上面的注释可以得出大概的印象:

  1. 内部依靠先入先出(FIFO) 等待队列。
  2. 存在 state 表示状态信息。state 值只能用 getState 、setState 和 compareAndSetState 方法以原子方式更新。
  3. 支持独占模式和共享模式,但具体需要子类实现具体支持哪种模式。
  4. 嵌套 AbstractQueuedSynchronizer.ConditionObject 可以作为 Condition 由子类实现。
  5. 子类需要重新定义 tryAcquire、tryRelease、tryAcquireShared、tryReleaseShared、isHeldExclusively 方法。

    队列节点 Node

    AQS - 图2
    Node节点,包含以下元素:
元素 含义
prev 上一个节点
next 下一个节点
thread 持有线程
waitStatus 节点状态
nextWaiter 下一个处于 CONDITION 状态的节点

组合成等待队列则如下:
AQS - 图3
下面是等待队列节点的 Node 属性:

  1. static final class Node {
  2. // 节点正在共享模式下等待的标记
  3. static final Node SHARED = new Node();
  4. // 指示节点正在以独占模式等待的标记
  5. static final Node EXCLUSIVE = null;
  6. // 指示线程已取消
  7. static final int CANCELLED = 1;
  8. // 指示后续线程需要唤醒
  9. static final int SIGNAL = -1;
  10. // 指示线程正在等待条件
  11. static final int CONDITION = -2;
  12. // 指示下一次acquireShared应该无条件传播
  13. static final int PROPAGATE = -3;
  14. /**
  15. * 状态字段,仅使用以下值
  16. * SIGNAL -1 :当前节点释放或者取消时,必须 unpark 他的后续节点。
  17. * CANCELLED 1 :由于超时(timeout)或中断(interrupt),该节点被取消。节点永远不会离开此状态。特别是,具有取消节点的线程永远不会再次阻塞。
  18. * CONDITION -2 :该节点目前在条件队列。 但它不会被用作同步队列节点,直到转移,转移时的状态将被设置为 0 。
  19. * PROPAGATE -3 :releaseShared 应该被传播到其他节点。
  20. * 0:都不是
  21. * 值以数字表示以简化使用,大多数时候可以检查符号(是否大于0)以简化使用
  22. */
  23. volatile int waitStatus;
  24. // 上一个节点
  25. volatile Node prev;
  26. // 下一个节点
  27. volatile Node next;
  28. // 节点持有线程
  29. volatile Thread thread;
  30. // 链接下一个等待条件节点,或特殊值共享
  31. Node nextWaiter;
  32. // 节点是否处于 共享状态 是, 返回 true
  33. final boolean isShared() {
  34. return nextWaiter == SHARED;
  35. }
  36. // 返回前一个节点, 使用时 前一个节点不能为空
  37. final Node predecessor() throws NullPointerException {
  38. Node p = prev;
  39. if (p == null)
  40. throw new NullPointerException();
  41. else
  42. return p;
  43. }
  44. Node() { // Used to establish initial head or SHARED marker
  45. }
  46. Node(Thread thread, Node mode) { // Used by addWaiter
  47. this.nextWaiter = mode;
  48. this.thread = thread;
  49. }
  50. Node(Thread thread, int waitStatus) { // Used by Condition
  51. this.waitStatus = waitStatus;
  52. this.thread = thread;
  53. }
  54. }

在 Node 节点中需要重点关注 waitStatus

  1. 默认状态为 0;
  2. waitStatus > 0 (CANCELLED 1) 说明该节点超时或者中断了,需要从队列中移除;
  3. waitStatus = -1 SIGNAL 当前线程的前一个节点的状态为 SIGNAL,则当前线程需要阻塞(unpark);
  4. waitStatus = -2 CONDITION -2 :该节点目前在条件队列;
  5. waitStatus = -3 PROPAGATE -3 :releaseShared 应该被传播到其他节点,在共享锁模式下使用。

了解完 Node 的结构之后,再了解下 AQS 结构,并从常用方法入手,逐步了解具体实现逻辑。

AbstractQueuedSynchronizer

  1. public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
  2. // 等待队列的头,延迟初始化。 除了初始化,它是仅经由方法setHead修改。 注意:如果头存在,其waitStatus保证不会是 CANCELLED 状态
  3. private transient volatile Node head;
  4. // 等待队列的尾部,延迟初始化。 仅在修改通过方法ENQ添加新节点等待。
  5. private transient volatile Node tail;
  6. // 同步状态
  7. private volatile int state;
  8. // 获取状态
  9. protected final int getState() {
  10. return state;
  11. }
  12. // 设置状态值
  13. protected final void setState(int newState) {
  14. state = newState;
  15. }
  16. // 原子更新状态值
  17. protected final boolean compareAndSetState(int expect, int update) {
  18. // See below for intrinsics setup to support this
  19. return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
  20. }
  21. }

在 AQS 中主要参数为:

参数 含义
head 等待队列头
tail 等待队列尾
state 同步状态

通过注释了解到,在 AQS 里主要分为两种操作模式,分别是:独占模式、共享模式,下面分别从两个不同的角度去分析源码。

操作 含义
acquire 以独占模式获取,忽略中断。 通过调用至少一次实施tryAcquire ,在成功时返回。 否则,线程排队,可能重复查封和解封,调用tryAcquire直到成功为止。 这种方法可以用来实现方法Lock.lock 。
release 以独占模式释放。 通过疏通一个或多个线程,如果实现tryRelease返回true。 这种方法可以用来实现方法Lock.unlock 。
acquireShared 获取在共享模式下,忽略中断。 通过至少一次第一调用实现tryAcquireShared ,在成功时返回。 否则,线程排队,可能重复查封和解封,调用tryAcquireShared直到成功为止。
releaseShared 以共享模式释放。 通过疏通一个或多个线程,如果实现tryReleaseShared返回true。

无论是共享模式还是独占模式在这里面都会用到 addWaiter 方法,将当前线程及模式创建排队节点。

独占模式

获取独占资源 acquire
  1. public final void acquire(int arg) {
  2. // tryAcquire 尝试获取 state,获取失败则会加入到队列
  3. if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
  4. selfInterrupt();
  5. }

在独占模式下会尝试获取 state,当获取失败时会调用 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)。

  1. tryAcquire(arg),尝试获取 state 这块由子类自己实现,不同的子类逻辑不同,这块在介绍子类代码时会说明。
  2. 获取 state 失败后,会进行 acquireQueued(addWaiter(Node.EXCLUSIVE), arg),这块代码可以拆分为两块:addWaiter(Node.EXCLUSIVE),acquireQueued(node, arg)。
  3. addWaiter(Node.EXCLUSIVE) 返回的是当前新创建的节点。
  4. acquireQueued(node, arg) 线程获取锁失败,使用 addWaiter(Node.EXCLUSIVE) 放入等待队列,而 acquireQueued(node, arg) 使用循环,不断的为队列中的节点去尝试获取资源,直到获取成功或者被中断。

总结获取资源主要分为三步:

  1. 尝试获取资源
  2. 入队列
  3. 出队列

尝试获取资源 tryAcquire(arg),由子类实现,那下面则着手分别分析 入队列出队列

入队列:addWaiter(Node.EXCLUSIVE)

使用 addWaiter(Node.EXCLUSIVE) 方法将节点插入到队列中,步骤如下:

  1. 根据传入的模式创建节点
  2. 判断尾节点是否存在,不存在则需要使用 enq(node) 方法初始化节点,存在则直接尝试插入尾部。
  3. 尝试插入尾部时使用 CAS 插入,防止并发情况,如果插入失败,会调用 enq(node) 自旋直到插入。

    1. private Node addWaiter(Node mode) {
    2. Node node = new Node(Thread.currentThread(), mode);
    3. // 定位到队列末尾的 node
    4. Node pred = tail;
    5. if (pred != null) {
    6. // 新节点的上一个节点 指向尾节点
    7. node.prev = pred;
    8. // 使用 CAS 设置尾节点,tail 如果等于 pred 则更新为 node
    9. if (compareAndSetTail(pred, node)) {
    10. // 更新成功则将 pred 的下一个节点指向 node
    11. pred.next = node;
    12. return node;
    13. }
    14. }
    15. // 尾节点没有初始化,或竞争失败,自旋
    16. enq(node);
    17. return node;
    18. }
    19. /**
    20. * tailOffset 也就是成员变量 tail 的值
    21. * 此处相当于:比较 tail 的值和 expect 的值是否相等, 相等则更新为 update
    22. */
    23. private final boolean compareAndSetTail(Node expect, Node update) {
    24. return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
    25. }
    26. private final boolean compareAndSetHead(Node update) {
    27. return unsafe.compareAndSwapObject(this, headOffset, null, update);
    28. }
    29. private Node enq(final Node node) {
    30. for (;;) {
    31. Node t = tail;
    32. // 尾节点为空 需要初始化头节点,此时头尾节点是一个
    33. if (t == null) { // Must initialize
    34. if (compareAndSetHead(new Node()))
    35. tail = head;
    36. } else {
    37. // 不为空 循环赋值
    38. node.prev = t;
    39. if (compareAndSetTail(t, node)) {
    40. t.next = node;
    41. return t;
    42. }
    43. }
    44. }
    45. }

    看完代码和注释肯定还是有点模糊,现在用图一步一步进行说明。
    因为根据初始尾节点是否为空分为两种情况,这里使用两幅图:

  4. 第一幅为第一次添加节点,此时 head 会延迟初始化;

  5. 第二幅图为已经存在队列,进行插入节点;
  6. 注意看代码,enq 方法返回的是之前的尾节点
  7. addWaiter 方法 返回的是当前插入的新创建的节点

AQS - 图4
AQS - 图5
节点添加到队列之后,返回当前节点,而下一步则需要调用方法 acquireQueued(addWaiter(Node.EXCLUSIVE), arg) 不断的去获取资源。

出队列:acquireQueued(addWaiter(Node.EXCLUSIVE), arg)

方法会通过循环不断尝试获取拿到资源,直到成功。代码如下:

  1. final boolean acquireQueued(final Node node, int arg) {
  2. // 是否拿到资源
  3. boolean failed = true;
  4. try {
  5. // 中断状态
  6. boolean interrupted = false;
  7. // 无限循环
  8. for (;;) {
  9. // 当前节点之前的节点
  10. final Node p = node.predecessor();
  11. // 前一个节点是头节点, 说明当前节点是 头节点的 next 即真实的第一个数据节点 (因为 head 是虚拟节点)
  12. // 然后再尝试获取资源
  13. if (p == head && tryAcquire(arg)) {
  14. // 获取成功之后 将头指针指向当前节点
  15. setHead(node);
  16. p.next = null; // help GC
  17. failed = false;
  18. return interrupted;
  19. }
  20. // p 不是头节点, 或者 头节点未能获取到资源 (非公平情况下被别的节点抢占)
  21. // 判断 node 是否要被阻塞,
  22. if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
  23. interrupted = true;
  24. }
  25. } finally {
  26. if (failed)
  27. cancelAcquire(node);
  28. }
  29. }
  1. 不断获取本节点的上一个节点是否为 head,因为 head 是虚拟节点,如果当前节点的上一个节点是 head 节点,则当前节点为 第一个数据节点
  2. 第一个数据节点不断的去获取资源,获取成功,则将 head 指向当前节点;
  3. 当前节点不是头节点,或者 tryAcquire(arg) 失败(失败可能是非公平锁)。这时候需要判断前一个节点状态决定当前节点是否要被阻塞(前一个节点状态是否为 SIGNAL)。

    1. /**
    2. * 根据上一个节点的状态,判断当前线程是否应该被阻塞
    3. * SIGNAL -1 :当前节点释放或者取消时,必须 unpark 他的后续节点。
    4. * CANCELLED 1 :由于超时(timeout)或中断(interrupt),该节点被取消。节点永远不会离开此状态。特别是,具有取消节点的线程永远不会再次阻塞。
    5. * CONDITION -2 :该节点目前在条件队列。 但它不会被用作同步队列节点,直到转移,转移时的状态将被设置为 0 。
    6. * PROPAGATE -3 :releaseShared 应该被传播到其他节点。
    7. * 0:都不是
    8. *
    9. */
    10. private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    11. // 前一个节点的等待状态
    12. int ws = pred.waitStatus;
    13. // 前一个节点需要 unpark 后续节点
    14. if (ws == Node.SIGNAL)
    15. return true;
    16. // 当前节点处于取消状态
    17. if (ws > 0) {
    18. do {
    19. // 将取消的节点从队列中移除
    20. node.prev = pred = pred.prev;
    21. } while (pred.waitStatus > 0);
    22. pred.next = node;
    23. } else {
    24. // 设置前一个节点为 SIGNAL 状态
    25. compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    26. }
    27. return false;
    28. }

    shouldParkAfterFailedAcquire 方法中,会判断前一个节点的状态,同时取消在队列中当前节点前面无效的节点。
    AQS - 图6
    再继续阅读 出队列 acquireQueued 方法,发现有一个 finally 会判断状态后执行 cancelAcquire(node); ,也就是上面流程图中下面的红色方块。

    cancelAcquire(Node node)
    1. final boolean acquireQueued(final Node node, int arg) {
    2. // 是否拿到资源
    3. boolean failed = true;
    4. try {
    5. // 省略
    6. // 在 finally 会将当前节点置为取消状态
    7. } finally {
    8. if (failed)
    9. cancelAcquire(node);
    10. }
    11. }
    12. private void cancelAcquire(Node node) {
    13. // 节点不存在 直接返回
    14. if (node == null)
    15. return;
    16. // 取消节点关联线程
    17. node.thread = null;
    18. //跳过已经取消的节点,获取当前节点之前的有效节点
    19. Node pred = node.prev;
    20. while (pred.waitStatus > 0)
    21. node.prev = pred = pred.prev;
    22. // 获取当前节点之前的有效节点的下一个节点
    23. Node predNext = pred.next;
    24. // 当前节点设置为取消
    25. node.waitStatus = Node.CANCELLED;
    26. // 当前节点如果是尾节点,则将最后一个有效节点设置为尾节点,并将 predNext 设置为空
    27. if (node == tail && compareAndSetTail(node, pred)) {
    28. compareAndSetNext(pred, predNext, null);
    29. } else {
    30. int ws;
    31. // pred 不是头节点(node 的上一个有效节点 不是 head) && ( pred的状态是 SIGNAL || pred 的状态设置为 SIGNAL 成功 ) && pred 的绑定线程不为空
    32. if (pred != head &&
    33. ((ws = pred.waitStatus) == Node.SIGNAL || (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
    34. pred.thread != null) {
    35. // 当前节点的后继节点
    36. Node next = node.next;
    37. // 后继节点不为空 且 状态有效 将 pred 的 后继节点设置为 当前节点的后继节点
    38. if (next != null && next.waitStatus <= 0)
    39. compareAndSetNext(pred, predNext, next);
    40. } else {
    41. // node 的上一个有效节点 是 head, 或者其他情况 唤醒当前节点的下一个有效节点
    42. unparkSuccessor(node);
    43. }
    44. node.next = node; // help GC
    45. }
    46. }
    47. private void unparkSuccessor(Node node) {
    48. // 判断当前节点状态
    49. int ws = node.waitStatus;
    50. if (ws < 0)
    51. // 将节点状态更新为 0
    52. compareAndSetWaitStatus(node, ws, 0);
    53. // 下一个节点, 一般是下一个节点应该就是需要唤醒的节点,即颁发证书。
    54. Node s = node.next;
    55. // 大于 0 CANCELLED : 线程已取消
    56. // 但是有可能 后继节点 为空或者被取消了。
    57. if (s == null || s.waitStatus > 0) {
    58. s = null;
    59. // 从尾节点开始遍历,直到定位到 t.waitStatus <= 0 的节点
    60. // 定位到后并不会停止,会继续执行,相当于找到最开始的那个需要唤醒的节点
    61. // t.waitStatus <= 0 : SIGNAL( -1 后续线程需要释放)
    62. // CONDITION ( -2 线程正在等待条件)
    63. // PROPAGATE ( -3 releaseShared 应该被传播到其他节点)
    64. for (Node t = tail; t != null && t != node; t = t.prev)
    65. if (t.waitStatus <= 0)
    66. s = t;
    67. }
    68. // 定位到需要唤醒的节点后 进行 unpark
    69. if (s != null)
    70. LockSupport.unpark(s.thread);
    71. }

    流程分析:

  4. 找到当前节点的前一个非无效节点 pred;

  5. 当前节点如果是尾节点,则将最后一个有效节点设置为尾节点,并将 predNext 设置为空;
  6. pred 不是头节点 && ( pred的状态是 SIGNAL || pred 的状态设置为 SIGNAL 成功 ) && pred 的绑定线程不为空;
  7. 其他情况。

下面分别画图:
AQS - 图7
AQS - 图8
AQS - 图9
Q: 通过图可以看出来,只操作了 next 指针,但是没有操作 prev 指针,这是为什么呢?
A:出队列:acquireQueued(addWaiter(Node.EXCLUSIVE), arg) 方法中,shouldParkAfterFailedAcquire 方法会判断前一个节点的状态,同时取消在队列中当前节点前面无效的节点。这时候会移除之前的无效节点,此处也是为了防止指向一个已经被移除的节点。同时保证 prev 的稳定,有利于从 tail 开始遍历列表,这块在 unparkSuccessor(node); 中也可以看到是从后往前表里列表。
Q: unparkSuccessor(Node node) 为什么从后往前遍历?
A:
AQS - 图10
addWaiter(Node.EXCLUSIVE) 插入新节点时,使用的是 尾插法,看红框部分,此时有可能还未指向next。
Q: node.next = node; 这块导致 head不是指向最新节点,链表不就断了么?
A: acquireQueued 方法介绍中,里面有个循环,会不断尝试获取资源,成功之后会设置为 head。并且在 shouldParkAfterFailedAcquire 中也会清除当前节点前的无效节点。

释放独占资源 release
  1. public final boolean release(int arg) {
  2. if (tryRelease(arg)) {
  3. Node h = head;
  4. if (h != null && h.waitStatus != 0)
  5. unparkSuccessor(h);
  6. return true;
  7. }
  8. return false;
  9. }

以独占模式释放。 通过释放一个或多个线程,如果实现tryRelease返回true。 这种方法可以用来实现方法Lock.unlock 。

  1. tryRelease(arg) 操作释放资源,同样是由子类实现,后面介绍子类时会进行说明。返回 true 说明资源现在已经没有线程持有了,其他节点可以尝试获取;
  2. 释放成功,且 head != null && h.waitStatus != 0, 会继续执行 unparkSuccessor(h);
  3. 这块会看到 只要 tryRelease(arg) 操作释放资源成功, 后面无论执行是否成功,都会返回 true,unparkSuccessor(h) 相当于只是附加操作。

    共享模式

    获取共享资源 acquireShared
    1. public final void acquireShared(int arg) {
    2. // 小于 0 表示获取资源失败
    3. if (tryAcquireShared(arg) < 0)
    4. doAcquireShared(arg);
    5. }
    6. private void doAcquireShared(int arg) {
    7. // 添加到节点 此处是共享节点
    8. final Node node = addWaiter(Node.SHARED);
    9. // 根据是否拿到资源 判断是否需要取消
    10. boolean failed = true;
    11. try {
    12. boolean interrupted = false;
    13. for (;;) {
    14. // 返回前一个节点
    15. final Node p = node.predecessor();
    16. if (p == head) {
    17. // 再次尝试获取共享资源
    18. int r = tryAcquireShared(arg);
    19. // 表示获取成功
    20. if (r >= 0) {
    21. // 设置当前节点为头节点 并尝试唤醒后续节点
    22. setHeadAndPropagate(node, r);
    23. // 释放头节点 GC 会回收
    24. p.next = null; // help GC
    25. if (interrupted)
    26. selfInterrupt();
    27. failed = false;
    28. return;
    29. }
    30. }
    31. if (shouldParkAfterFailedAcquire(p, node) &&
    32. parkAndCheckInterrupt())
    33. interrupted = true;
    34. }
    35. } finally {
    36. if (failed)
    37. cancelAcquire(node);
    38. }
    39. }
  4. tryAcquireShared(arg),尝试获取资源,这块由子类实现;

  5. 返回值分为 3 种:
    1. 小于 0: 表示失败;
    2. 等于 0: 表示共享模式获取资源成功,但后续的节点不能以共享模式获取成功;
    3. 大于 0: 表示共享模式获取资源成功,后续节点在共享模式获取也可能会成功,在这种情况下,后续等待线程必须检查可用性。
  6. 在失败后会使用 doAcquireShared(arg); 不断获取资源;
  7. final Node node = addWaiter(Node.SHARED); 同样会创建节点;
  8. 在循环中不断判断前一个节点如果是 head,则尝试获取资源;
  9. 在共享模式下获取到资源后会使用 setHeadAndPropagate(node, r); 设置头节点,同时唤醒后续节点。

    设置头节点,并传播唤醒后续节点
    1. // node 是当前节点
    2. // propagate 是 前一步 tryAcquireShared 的返回值 进来时 >=0
    3. // 大于 0: 表示共享模式获取资源成功,后续节点在共享模式获取也可能会成功,在这种情况下,后续等待线程必须检查可用性。
    4. private void setHeadAndPropagate(Node node, int propagate) {
    5. // 记录下当前头节点
    6. Node h = head; // Record old head for check below
    7. // 设置传入 node 为头节点
    8. setHead(node);
    9. // 判断条件,唤醒后续节点
    10. // propagate > 0 有后续资源
    11. // h == null 旧的头节点 因为前面 addWaiter, 肯定不会为空,应该是防止 h.waitStatus < 0 空指针的写法
    12. // (h = head) == null 当前的 头节点,再判断状态
    13. // waitStatus < 0 后续节点就需要被唤醒
    14. if (propagate > 0 || h == null || h.waitStatus < 0 ||
    15. (h = head) == null || h.waitStatus < 0) {
    16. Node s = node.next;
    17. // 后续节点为共享,则需要唤醒
    18. if (s == null || s.isShared())
    19. doReleaseShared();
    20. }
    21. }

    doReleaseShared() 释放共享资源
    1. private void doReleaseShared() {
    2. // 循环
    3. for (;;) {
    4. // 从头开始
    5. Node h = head;
    6. // 判断队列是否为空,就是刚初始化
    7. if (h != null && h != tail) {
    8. int ws = h.waitStatus;
    9. // SIGNAL( -1 后续线程需要释放)
    10. if (ws == Node.SIGNAL) {
    11. // 将等待状态更新为 0 如果失败,会循环
    12. if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
    13. continue; // loop to recheck cases
    14. // 唤醒后续节点, 同时将当前节点设置为 取消
    15. unparkSuccessor(h);
    16. }
    17. // 如果状态是 0 则会更新状态为 PROPAGATE
    18. // PROPAGATE ( -3 releaseShared 应该被传播到其他节点)
    19. else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
    20. continue; // loop on failed CAS
    21. }
    22. // 判断头节点有没有变化,有变化 是因为竞争,别的线程获取到了锁,会继续循环
    23. // 没有变化直接结束
    24. if (h == head) // loop if head changed
    25. break;
    26. }
    27. }
  10. 从头节点开始进行,如果 h != null && h != tail 说明队列不是空或者刚初始化;

  11. 节点状态为 SIGNAL( -1 )说明后续线程需要释放;
  12. 会更改当前节点状态,成功后唤醒后续节点,失败则继续循环;
  13. 节点状态如果是 0 则更新为 PROPAGATE,会将状态传播。
    释放共享资源 releaseShared
    1. public final boolean releaseShared(int arg) {
    2. if (tryReleaseShared(arg)) {
    3. // 释放共享资源
    4. doReleaseShared();
    5. return true;
    6. }
    7. return false;
    8. }
    以共享模式释放。 通过释放一个或多个线程,如果实现tryReleaseShared返回true。

    总结

    Q: AQS 到底是什么?
    A: AQS 内部提供了一个先入先出(FIFO)双向等待队列,内部依靠 Node 实现,并提供了在独占模式共享模式下的出入队列的公共方法。而关于状态信息 state 的定义是由子类实现。tryAcquire、tryRelease、tryAcquireShared、tryReleaseShared等尝试获取资源操作都是由子类进行定义和实现的。而 AQS 中提供了子类获取资源之后的相关操作,包括节点 Node 的出入队列,自旋获取资源等等。
    Q: AQS 获取资源失败后会如何操作?
    A: 线程获取资源失败后,会放到等待队列中,在队列中会不断尝试获取资源(自旋),说明线程只是进入等待状态,后面还是可以再次获取资源的。
    Q: AQS 等待队列的数据结构是什么?
    A: CLH变体的先入先出(FIFO)双向等待队列。(CLH锁是一个自旋锁。能确保无饥饿性。提供先来先服务的公平性。是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程仅仅在本地变量上自旋,它不断轮询前驱的状态,如果发现前驱释放了锁就结束自旋。)
    Q: AQS 等待队列中的节点如何获取获取和释放资源的?
    A: 可以看下独占模式中的讲述过程,通过代码梳理。
    本文分别从 独占模式共享模式介绍的 AQS 基本逻辑,并通过源码和作图理解基本思路。但是并没有对需要子类实现的业务逻辑做介绍。这块会在后面介绍 ReentrantLockCountDownLatch 等子类的时候做介绍。