若是你读过 JUC 中 ReentrantLock、CountDownLatch、FutureTask、Semaphore 等的源代码,会发现其中都有一个名为 Sync 的类,而这个类是以 AbstractQueuedSynchronizer 为基础的,因此说 AbstractQueuedSynchronizer 是 JUC 的基础之一(注:CyclicBarrier 并无直接以 AQS 为基础)。出于知其然也要知其因此然的目的,我学习了 AQS 的实现原理,并总结成此文。 java
在 AQS 中,有两个重要的数据结构,一个是 volatile int state,另外一个是 class Node 组成的双向链表。 node
顾名思义,这个变量是用来表示 AQS 的状态的,例如 ReentrantLock 的锁的状态和重入次数、FutureTask 中任务的状态、CountDownLatch 中的 count 计数等等。这个值的更新都是由 AQS compareAndSetState 方法来实现的,而这个方法则是经过 Compare and Swap 算法实现,至于这个算法的细节就很少说了。在 JDK 中,这个算法是由 Native 方法实现的。 算法
Node 是 AQS 的一个内部类,主要有 waitStatus、prev、next、thread 等这么几个属性。不介绍,从名字你们也能知道这些属性的用途。 数据结构
指向 Node 链表的头部。可为空、一个没用引用线程对象的空 Node、一个引用当前占有 AQS 的线程对象的 Node。 ide
指向 Node 链表的尾部。 学习
这个方法自己的代码并不长,可是流程提及来也不简单。 ui
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
首先尝试调用 tryAcquire(int) 方法来获取(锁等等,具体获取什么取决于你将 AQS 应用在何种场景中)。tryAcquire 这个方法是抽象方法,具体行为须要由子类来实现。在 ReentrantLock 内部类 Sync 实现中,这个方法经过 CAS 算法设置锁的状态,用 AQS 中的 state 表示锁被重入的次数。 spa
若是 tryAcquire 成功了,那也就没什么了,整个 acquire 操做也就成功了。若是 tryAcquire 失败,那就须要把当前线程作入队操做。这个入队操做是由 Node addWaiter(Node mode) 方法来实现的。这个方法作的事情并不复杂,就是将当前线程(由于它没有 acquire 成功)放入队列的尾部。若是队列是空的,则在作入队操做以前先初始化队列。队列的头节点并不引用任何线程对象或者其引用的线程对象获取的当前的这个 AQS。总之,head 中的线程对象引用都是没有被挂起的(null 天然不会被挂起)。 线程
在入队操做成功以后,会再对刚刚入队的线程作一次 acquire 操做。这样作的目的是为了应对短暂竞争的场景,尽可能避免挂起线程的操做。 翻译
final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return interrupted; }
上面这段代码就是让已入队的线程对象作 acquire 操做。p.next = null 的目的在于当 node 所引用的节点须要回收时加快内存回收的速度。
若是刚入队的节点没有 acquire 成功,那这个 Node (实际上是这个 Node 所引用的线程) 十有八九将被挂起。判断的条件是这个 Node 的 waitStatus,这个状态必须设成 SIGNAL,就是告诉别人我要被挂起了,等大家 release 的时候记得叫一下兄弟。若是状态等于0,那就把状态设置成 SIGNAL。这以后便把当前线程挂起,再而后天然就没有而后了,直到被 release。
接下来再说说 release 的过程。release 的过程相对简单,和 acquire 相似,首先进行 tryRelease 操做。仍是以 ReentrantLock 为例,tryRelease 会首先判断当前线程是否 acquire 了 AQS,若是是,则改变 AQS 的状态。而后在尝试恢复一个被挂起的线程,一般是 head 的 next 节点所引用的线程对象。
在 AQS 中有一个 int 类型的 volatile 变量 state,使用 AQS 的类能够自定义 state 对其的含义。例如,ReentrantLock 用 0 表示没有线程获取锁,大于 0 则表示重入锁的重入次数;Semaphore 用来表示许可数量;FutureTask 用来表示任务状态,例如运行中、已完成等。
在扩展 AQS 时,子类须要根据本身的需求在诸如 tryAcquire 方法中使用 compareAndSetState 方法设置相应的状态值。
处于独占模式下时,其余线程试图获取该锁将没法取得成功。在共享模式下,多个线程获取某个锁可能(但不是必定)会得到成功。此类并不“了解”这些不一样,除了机械地意识到当在共享模式下成功获取某一锁时,下一个等待线程(若是存在)也必须肯定本身是否能够成功获取该锁。
上面这段话是摘自 JDK API。说实话,这段话说的很不明白,只看这段话我也没明白,因此仍是去看这两个模式如何在实际中去应用。以采用 Shared 模式使用 AQS 的 CountDownLatch 为例,它采用 acquireShared 和 releaseShared 做为其业务方法。如同 acquire 和 release 这两个方法,acquireShared 和 releaseShared 也会调用 tryAcquireShared 和 tryReleaseShared 这两个须要由子类实现的方法。
以 CountDownLatch 为例,它的 tryAcquireShared 的实现以下:
protected int tryAcquireShared(int acquires) { return (getState() == 0) ? 1 : -1; }
是否是很是简单。对比一下 ReentrantLock 的 tryAcquire 方法的实现,我这里就不贴出源代码了。但即便不看源代码,咱们也知道,ReentrantLock 的 tryAcquire 方法是排他的。可是看一下 CountDownLatch 的 tryAcquireShared 方法的实现,彻底看不出排他性的体现。其实稍加注意就会发现,tryAcquire 和 tryAcquireShared 的方法定义存在一个巨大的不一样,就是返回值的不一样。tryAcquire 返回的是 boolean 类型,其分别表示 acquire 成功或失败,而 tryAcquireShared 返回的倒是 int 类型,负、零、正表明三种含义:失败、独占获取、共享获取。这就是 AQS 文档中对独占模式和共享模式描述中的那段“可能(但不是必定)”的缘由。
经过阅读 CountDownLatch 的源代码和我上面的讲解,我想大部分人应该都能理解 AQS 独占模式和共享模式的含义了。