本人CSDN地址:blog.csdn.net/qq_43731074…
AQS是指JUC包下的AbstractQueuedSynchronizer的简称,它是Java锁以及一些同步器(JUC包)实现的核心,大部分JUC下的类内部的提供了一个实现了AQS的子类来提供辅助。
AQS还继承了一个AbstractOwnableSynchronizer类,它是AQS的实现的重要基础。
AQS实现主要是依赖一个先进先出(FIFO)的等待队列和一个volatile标记的int型state状态标记。
AQS的FIFO等待队列,是基于双向链表实现的,是典型的CLH队列改造而来
首先来看一下AQS类的一下成员变量
1 | java复制代码public abstract class AbstractQueuedSynchronizer |
下面看看node类是如何实现的
1 | java复制代码 static final class Node { |
AQS 的大体结构图:
下面以分析ReentrantLock为例分析分析独占模式下,整个获取同步状态成功以及失败的处理过程:
1 | java复制代码 public final void acquire(int arg) { |
该方法主要是用于获取同步状态,主要分为几大步,第一步先通过tryAcquire(arg)第一次尝试获取同步状态,该方法AQS并没有实现,而是在相应的子类中实现比如Reentrantlock内部为了实现公平锁和非公平锁分别实现了不同逻辑的tryAcquire,如果该方法尝试获取同步状态失败,就进行第二步进入addWaiter(Node.EXCLUSIVE),该方法主要是把当前获取不到同步状态的线程进行封装成功一个node节点然后进入等待队列中,第三步就是进入等待队列之后,还好重新通过自旋尝试获取同步状态,自旋到一定程度,发现还是没有获取到同步状态则通过LockSupport类进行把线程挂起操作。如果中间出现被中断则线程进行自我中断。
接着看addWaiter方法
1 | java复制代码 /** |
addWaiter方法主要是把无法获取到同步状态的线程封装成一个节点然后进入等待队列,如果是第一次入队,则擦黄金一个空节点作为哨兵节点用于唤醒后继节点的线程,如果不是第一次入队,则把当前线程的封装的节点从尾部添加,然后通过cas把该节点设置为尾节点。
下面来看看enq是如何实现:
1 | java复制代码 private Node enq(final Node node) { |
整个方法逻辑:首先通过自旋判断尾节点是否为空,如果为空则创建一个空节点,并且设置为头节点并且尾节点指向头节点,当再次循环的时候,此时尾节点已经不是空了,这时候把传入的node节点进行节点添加,并把设置为尾节点。
接着看acquireQueued在addwaiter方法添加线程进入等待队列是如何通过该方法重新尝试获取同步状态以及失败后是如何处理的。
1 | java复制代码 final boolean acquireQueued(final Node node, int arg) { |
该方法主要逻辑:首先判断当前节点的前驱节点是否是头节点并且通过tryacquire方法再次获取同步状态如果成功则表明该线程是第一个线程则直接执行,并把该节点设置为新的头节点,并把前驱节点断开以便于gc回收,然后返回,第二种情况就是非第一个线程,在进入shouldParkAfterFailedAcquire方法判断是否适合挂起,如果适合则调用parkAndCheckInterrupt方法去执行 LockSupport.park(this);进行挂起操作。
接着看看shouldParkAfterFailedAcquire是如何判断是否应该挂起的
1 | java复制代码 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { |
判断释放适合挂起方法,分三步,第一步判断前驱节点释放状态之后会唤醒node节点,则可以安全的挂起,第二步,如果前驱节点已经放弃获取,则通循环获取第一个还没放弃获取的节点然后让node节点接上去。第三步,如果前驱节点正处于持有同步状态或者是共享模式,则需要设置前驱节点的ws状态标记为SIGNAL表示释放同步状态后唤醒该后承节点。
如果同步状态获取失败,挂起也失败了,则会执行取消获取方法cancelAcquire
1 | java复制代码 private void cancelAcquire(Node node) { |
接着看unparkSuccessor是如何唤醒后继节点的
1 | java复制代码 private void unparkSuccessor(Node node) { |
下面分析AQS中release()方法是如何释放同步状态并且唤醒线程来执行的
1 | java复制代码 public final boolean release(int arg) { |
到这里,独占模式同步状态的获取以及释放过程都分析完,其他的待续。。。。
本文转载自: 掘金