AQS
上图中有颜色的为Method,无颜色的为Attribution。 总的来说,AQS框架共分为五层,自上而下由浅入深,从AQS对外暴露的API到底层基础数据。 当有自定义同步器接入时,只需重写第一层所需要的部分方法即可,不需要关注底层具体的实现流程。当自定义同步器进行加锁或者解锁操作时,先经过第一层的API进入AQS内部方法,然后经过第二层进行锁的获取,接着对于获取锁失败的流程,进入第三层和第四层的等待队列处理,而这些处理方式均依赖于第五层的基础数据提供层。 原理概览 AQS核心思想是,如果被请求的共享资源空闲,那么就将当前请求资源的线程设置为有效的工作线程,将共享资源设置为锁定状态;如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁分配。这个机制主要用的是CLH队列的变体实现的,将暂时获取不到锁的线程加入到队列中。
CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。
AQS使用一个Volatile的int类型的成员变量来表示同步状态,通过内置的FIFO队列来完成资源获取的排队工作,通过CAS完成对State值的修改。
1
private volatile int state ; //共享变量,使用volatile修饰保证线程可见性
AQS数据结构 AQS数据结构
线程两种资源共享方式 Share(共享):多个线程可同时执行,如Semaphore/CountDownLatch。Semaphore、CountDownLatCh、 CyclicBarrier、ReadWriteLock。 Exclusive(独占):只有一个线程能执行,如ReentrantLock。又可分为公平锁和非公平锁:公平锁:按照线程在队列中的排队顺序,先到者先拿到锁 非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的 ReentrantReadWriteLock可以看成是组合式,因为ReentrantReadWriteLock是读写锁允许多个线程同时对某一资源进行读。
不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在上层已经帮我们实现好了。
属性值的含义 waitStatus 当前节点在队列中的状态 thread 表示处于该节点的线程 prev 前驱指针 predecessor 返回前驱节点,没有的话抛出npe nextWaiter 指向下一个处于CONDITION状态的节点(由于本篇文章不讲述Condition Queue队列,这个指针不多介绍) next 后继指针 waitStatus(节点状态) 0,表示当前节点在sync queue中,等待着获取锁。 SIGNAL 为-1,表示线程已经准备好了,就等资源释放了,表示当前节点的后继节点包含的线程需要运行,需要进行unpark操作。 CANCELLED 为1,表示线程获取锁的请求已经取消了 CONDITION 为-2,表示当前节点在等待condition,也就是在condition queue中,节点线程等待唤醒 PROPAGATE 为-3,表示当前场景下后续的acquireShared能够得以执行。 AQS独占模式加锁
AQS共享模式加锁
AQS重要方法与ReentrantLock的关联 方法 AQS使用了模板方法模式,自定义同步器时需要重写下面几个AQS提供的模板方法
protected boolean isHeldExclusively() 该线程是否正在独占资源。只有用到Condition才需要去实现它。 protected boolean tryAcquire(int arg) 独占方式。arg为获取锁的次数,尝试获取资源,成功则返回True,失败则返回False。 protected boolean tryRelease(int arg) 独占方式。arg为释放锁的次数,尝试释放资源,成功则返回True,失败则返回False。 protected int tryAcquireShared(int arg) 共享方式。arg为获取锁的次数,尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。 protected boolean tryReleaseShared(int arg) 共享方式。arg为释放锁的次数,尝试释放资源,如果释放后允许唤醒后续等待结点返回True,否则返回False。 默认情况下,每个方法都抛出 UnsupportedOperationException。 这些方法的实现必须是内部线程安全的,并且通常应该简短而不是阻塞 。AQS类中的其他方法都是final ,所以无法被其他类使用,只有这几个方法可以被其他类使用。
以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。
一般来说,自定义同步器要么是独占方式,要么是共享方式,它们也只需实现tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一种即可。AQS也支持自定义同步器同时实现独占和共享两种方式,如ReentrantReadWriteLock。ReentrantLock是独占锁,所以实现了tryAcquire-tryRelease。
以非公平锁为例,这里主要阐述一下非公平锁与AQS之间方法的关联之处,具体每一处核心方法的作用会在文章后面详细进行阐述。ReentrantLock加锁流程
为了帮助大家理解ReentrantLock和AQS之间方法的交互过程,以非公平锁为例,我们将加锁和解锁的交互流程单独拎出来强调一下,以便于对后续内容的理解。ReentrantLock流程梳理
加锁: 通过ReentrantLock的加锁方法Lock进行加锁操作。 会调用到内部类Sync的Lock方法,由于Sync#lock是抽象方法,根据ReentrantLock初始化选择的公平锁和非公平锁,执行相关内部类的Lock方法,本质上都会执行AQS的Acquire方法。 AQS的Acquire方法会执行tryAcquire方法,但是由于tryAcquire需要自定义同步器实现,因此执行了ReentrantLock中的tryAcquire方法,由于ReentrantLock是通过公平锁和非公平锁内部类实现的tryAcquire方法,因此会根据锁类型不同,执行不同的tryAcquire。 tryAcquire是获取锁逻辑,获取失败后,会执行框架AQS的后续逻辑,跟ReentrantLock自定义同步器无关。 解锁: 通过ReentrantLock的解锁方法Unlock进行解锁。 Unlock会调用内部类Sync的Release方法,该方法继承于AQS。 Release中会调用tryRelease方法,tryRelease需要自定义同步器实现,tryRelease只在ReentrantLock中的Sync实现,因此可以看出,释放锁的过程,并不区分是否为公平锁。 释放成功后,所有处理由AQS框架完成,与自定义同步器无关。 通过上面的描述,大概可以总结出ReentrantLock加锁解锁时API层核心方法的映射关系。ReentrantLock映射关系
通过ReentrantLock理解AQS ReentrantLock中公平锁和非公平锁在底层是相同的,这里以非公平锁为例进行分析。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// java.util.concurrent.locks.ReentrantLock
static final class NonfairSync extends Sync {
...
final void lock () {
if ( compareAndSetState ( 0 , 1 ))
setExclusiveOwnerThread ( Thread . currentThread ());
else
acquire ( 1 );
}
...
}
// java.util.concurrent.locks.AbstractQueuedSynchronizer
public final void acquire ( int arg ) {
if ( ! tryAcquire ( arg ) && acquireQueued ( addWaiter ( Node . EXCLUSIVE ), arg ))
selfInterrupt ();
}
// java.util.concurrent.locks.AbstractQueuedSynchronizer
protected boolean tryAcquire ( int arg ) {
throw new UnsupportedOperationException ();
}
可以看出,这里只是AQS的简单实现,具体获取锁的实现方法是由各自的公平锁和非公平锁单独实现的(以ReentrantLock为例)。如果该方法返回了True,则说明当前线程获取锁成功,就不用往后执行了;如果获取失败,就需要加入到等待队列中。下面会详细解释线程是何时以及怎样被加入进等待队列中的。
线程加入等待队列 加入队列的时机 当执行Acquire(1)时,会通过tryAcquire获取锁。在这种情况下,如果获取锁失败,就会调用addWaiter加入到等待队列中去。
如何加入队列 获取锁失败后,会执行addWaiter(Node.EXCLUSIVE)加入等待队列,具体实现方法如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// java.util.concurrent.locks.AbstractQueuedSynchronizer
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 ;
if ( pred != null ) {
node . prev = pred ;
if ( compareAndSetTail ( pred , node )) {
pred . next = node ;
return node ;
}
}
enq ( node );
return node ;
}
private final boolean compareAndSetTail ( Node expect , Node update ) {
return unsafe . compareAndSwapObject ( this , tailOffset , expect , update );
}
主要的流程如下:
通过当前的线程和锁模式新建一个节点。 Pred指针指向尾节点Tail。 将New中Node的Prev指针指向Pred。 通过compareAndSetTail方法,完成尾节点的设置。这个方法主要是对tailOffset和Expect进行比较,如果tailOffset的Node和Expect的Node地址是相同的,那么设置Tail的值为Update的值。 如果Pred指针是Null(说明等待队列中没有元素),或者当前Pred指针和Tail指向的位置不同(说明被别的线程已经修改),就需要看一下Enq的方法。 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// java.util.concurrent.locks.AbstractQueuedSynchronizer
private Node enq ( final Node node ) {
for (;;) {
Node t = tail ;
if ( t == null ) { // Must initialize
if ( compareAndSetHead ( new Node ()))
tail = head ;
} else {
node . prev = t ;
if ( compareAndSetTail ( t , node )) {
t . next = node ;
return t ;
}
}
}
}
如果没有被初始化,需要进行初始化一个头结点出来。但请注意,初始化的头结点并不是当前线程节点,而是调用了无参构造函数的节点。如果经历了初始化或者并发导致队列中有元素,则与之前的方法相同。其实,addWaiter就是一个在双端链表添加尾节点的操作,需要注意的是,双端链表的头结点是一个无参构造函数的头结点。
总结一下,线程获取锁的时候,过程大体如下:
当没有线程获取到锁时,线程1获取锁成功。 线程2申请锁,但是锁被线程1占有。 如果再有线程要获取锁,依次在队列中往后排队即可。 等待队列中线程出队列时机 回到最初的源码:
1
2
3
4
5
// java.util.concurrent.locks.AbstractQueuedSynchronizer
public final void acquire ( int arg ) {
if ( ! tryAcquire ( arg ) && acquireQueued ( addWaiter ( Node . EXCLUSIVE ), arg ))
selfInterrupt ();
}
上文解释了addWaiter方法,这个方法其实就是把对应的线程以Node的数据结构形式加入到双端队列里,返回的是一个包含该线程的Node。
而这个Node会作为参数,进入到acquireQueued方法中。acquireQueued方法可以对排队中的线程进行“获锁”操作。
总的来说,一个线程获取锁失败了,被放入等待队列,acquireQueued会把放入队列中的线程不断去获取锁,直到获取成功或者不再需要获取(中断)。
下面我们从“何时出队列?”和“如何出队列?”两个方向来分析一下acquireQueued源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// java.util.concurrent.locks.AbstractQueuedSynchronizer
final boolean acquireQueued ( final Node node , int arg ) {
// 标记是否成功拿到资源
boolean failed = true ;
try {
// 标记等待过程中是否中断过
boolean interrupted = false ;
// 开始自旋,要么获取锁,要么中断
for (;;) {
// 获取当前节点的前驱节点
final Node p = node . predecessor ();
// 如果p是头结点,说明当前节点在真实数据队列的首部,就尝试获取锁(别忘了头结点是虚节点)
if ( p == head && tryAcquire ( arg )) {
// 获取锁成功,头指针移动到当前node
setHead ( node );
p . next = null ; // help GC
failed = false ;
return interrupted ;
}
// 说明p为头节点且当前没有获取到锁(可能是非公平锁被抢占了)或者是p不为头结点,这个时候就要判断当前node是否要被阻塞(被阻塞条件:前驱节点的waitStatus为-1),防止无限循环浪费资源。具体两个方法下面细细分析
if ( shouldParkAfterFailedAcquire ( p , node ) && parkAndCheckInterrupt ())
interrupted = true ;
}
} finally {
if ( failed )
cancelAcquire ( node );
}
}
注:setHead方法是把当前节点置为虚节点,但并没有修改waitStatus,因为它是一直需要用的数据。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// java.util.concurrent.locks.AbstractQueuedSynchronizer
private void setHead ( Node node ) {
head = node ;
node . thread = null ;
node . prev = null ;
}
// java.util.concurrent.locks.AbstractQueuedSynchronizer
// 靠前驱节点判断当前线程是否应该被阻塞
private static boolean shouldParkAfterFailedAcquire ( Node pred , Node node ) {
// 获取头结点的节点状态
int ws = pred . waitStatus ;
// 说明头结点处于唤醒状态
if ( ws == Node . SIGNAL )
return true ;
// 通过枚举值我们知道waitStatus>0是取消状态
if ( ws > 0 ) {
do {
// 循环向前查找取消节点,把取消节点从队列中剔除
node . prev = pred = pred . prev ;
} while ( pred . waitStatus > 0 );
pred . next = node ;
} else {
// 设置前任节点等待状态为SIGNAL
compareAndSetWaitStatus ( pred , ws , Node . SIGNAL );
}
return false ;
}
parkAndCheckInterrupt主要用于挂起当前线程,阻塞调用栈,返回当前线程的中断状态。
1
2
3
4
5
// java.util.concurrent.locks.AbstractQueuedSynchronizer
private final boolean parkAndCheckInterrupt () {
LockSupport . park ( this );
return Thread . interrupted ();
}
上述方法的流程图如下:AQS加锁流程1
从上图可以看出,跳出当前循环的条件是当“前置节点是头结点,且当前线程获取锁成功”。为了防止因死循环导致CPU资源被浪费,我们会判断前置节点的状态来决定是否要将当前线程挂起,具体挂起流程用流程图表示如下(shouldParkAfterFailedAcquire流程) :
AQS加锁流程2
从队列中释放节点的疑虑打消了,那么又有新问题了:
shouldParkAfterFailedAcquire中取消节点是怎么生成的呢?什么时候会把一个节点的waitStatus设置为-1? 是在什么时间释放节点通知到被挂起的线程呢 CANCELLED状态节点生成 acquireQueued方法中的Finally代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// java.util.concurrent.locks.AbstractQueuedSynchronizer
final boolean acquireQueued ( final Node node , int arg ) {
boolean failed = true ;
try {
...
for (;;) {
final Node p = node . predecessor ();
if ( p == head && tryAcquire ( arg )) {
...
failed = false ;
...
}
...
} finally {
if ( failed )
cancelAcquire ( node );
}
}
通过cancelAcquire方法,将Node的状态标记为CANCELLED。接下来,我们逐行来分析这个方法的原理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// java.util.concurrent.locks.AbstractQueuedSynchronizer
private void cancelAcquire ( Node node ) {
// 将无效节点过滤
if ( node == null )
return ;
// 设置该节点不关联任何线程,也就是虚节点
node . thread = null ;
Node pred = node . prev ;
// 通过前驱节点,跳过取消状态的node
while ( pred . waitStatus > 0 )
node . prev = pred = pred . prev ;
// 获取过滤后的前驱节点的后继节点
Node predNext = pred . next ;
// 把当前node的状态设置为CANCELLED
node . waitStatus = Node . CANCELLED ;
// 如果当前节点是尾节点,将从后往前的第一个非取消状态的节点设置为尾节点
// 更新失败的话,则进入else,如果更新成功,将tail的后继节点设置为null
if ( node == tail && compareAndSetTail ( node , pred )) {
compareAndSetNext ( pred , predNext , null );
} else {
int ws ;
// 如果当前节点不是head的后继节点,1:判断当前节点前驱节点的是否为SIGNAL,2:如果不是,则把前驱节点设置为SINGAL看是否成功
// 如果1和2中有一个为true,再判断当前节点的线程是否为null
// 如果上述条件都满足,把当前节点的前驱节点的后继指针指向当前节点的后继节点
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 )
compareAndSetNext ( pred , predNext , next );
} else {
// 如果当前节点是head的后继节点,或者上述条件不满足,那就唤醒当前节点的后继节点
unparkSuccessor ( node );
}
node . next = node ; // help GC
}
}
当前的流程:
获取当前节点的前驱节点,如果前驱节点的状态是CANCELLED,那就一直往前遍历,找到第一个waitStatus <= 0的节点,将找到的Pred节点和当前Node关联,将当前Node设置为CANCELLED。 根据当前节点的位置,考虑以下三种情况: 当前节点是尾节点。 当前节点是Head的后继节点。 当前节点不是Head的后继节点,也不是尾节点。 如何解锁 我们已经剖析了加锁过程中的基本流程,接下来再对解锁的基本流程进行分析。由于ReentrantLock在解锁的时候,并不区分公平锁和非公平锁,所以我们直接看解锁的源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
// java.util.concurrent.locks.ReentrantLock
public void unlock () {
sync . release ( 1 );
}
// java.util.concurrent.locks.AbstractQueuedSynchronizer
// 可以看到,本质释放锁的地方,是通过框架来完成的。
public final boolean release ( int arg ) {
if ( tryRelease ( arg )) {
Node h = head ;
if ( h != null && h . waitStatus != 0 )
unparkSuccessor ( h );
return true ;
}
return false ;
}
//在ReentrantLock里面的公平锁和非公平锁的父类Sync定义了可重入锁的释放锁机制。
// java.util.concurrent.locks.ReentrantLock.Sync
// 方法返回当前锁是不是没有被线程持有
protected final boolean tryRelease ( int releases ) {
// 减少可重入次数
int c = getState () - releases ;
// 当前线程不是持有锁的线程,抛出异常
if ( Thread . currentThread () != getExclusiveOwnerThread ())
throw new IllegalMonitorStateException ();
boolean free = false ;
// 如果持有线程全部释放,将当前独占锁所有线程设置为null,并更新state
if ( c == 0 ) {
free = true ;
setExclusiveOwnerThread ( null );
}
setState ( c );
return free ;
}
// java.util.concurrent.locks.AbstractQueuedSynchronizer
public final boolean release ( int arg ) {
// 上边自定义的tryRelease如果返回true,说明该锁没有被任何线程持有
if ( tryRelease ( arg )) {
// 获取头结点
Node h = head ;
// 头结点不为空并且头结点的waitStatus不是初始化节点情况,解除线程挂起状态
if ( h != null && h . waitStatus != 0 )
unparkSuccessor ( h );
return true ;
}
return false ;
}
//再看一下unparkSuccessor方法
// java.util.concurrent.locks.AbstractQueuedSynchronizer
private void unparkSuccessor ( Node node ) {
// 获取头结点waitStatus
int ws = node . waitStatus ;
if ( ws < 0 )
compareAndSetWaitStatus ( node , ws , 0 );
// 获取当前节点的下一个节点
Node s = node . next ;
// 如果下个节点是null或者下个节点被cancelled,就找到队列最开始的非cancelled的节点
if ( s == null || s . waitStatus > 0 ) {
s = null ;
// 就从尾部节点开始找,到队首,找到队列第一个waitStatus<0的节点。
for ( Node t = tail ; t != null && t != node ; t = t . prev )
if ( t . waitStatus <= 0 )
s = t ;
}
// 如果当前节点的下个节点不为空,而且状态<=0,就把当前节点unpark
if ( s != null )
LockSupport . unpark ( s . thread );
}
// 为什么要从后往前找第一个非Cancelled的节点呢?原因如下。
// 之前的addWaiter方法:
// java.util.concurrent.locks.AbstractQueuedSynchronizer
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 ;
if ( pred != null ) {
node . prev = pred ;
if ( compareAndSetTail ( pred , node )) {
pred . next = node ;
return node ;
}
}
enq ( node );
return node ;
}
我们从这里可以看到,节点入队并不是原子操作,也就是说,node.prev = pred; compareAndSetTail(pred, node) 这两个地方可以看作Tail入队的原子操作,但是此时pred.next = node;还没执行,如果这个时候执行了unparkSuccessor方法,就没办法从前往后找了,所以需要从后往前找。还有一点原因,在产生CANCELLED状态节点的时候,先断开的是Next指针,Prev指针并未断开,因此也是必须要从后往前遍历才能够遍历完全部的Node。
综上所述,如果是从前往后找,由于极端情况下入队的非原子操作和CANCELLED节点产生过程中断开Next指针的操作,可能会导致无法遍历所有的节点。所以,唤醒对应的线程后,对应的线程就会继续往下执行。继续执行acquireQueued方法以后,中断如何处理?
中断恢复后的执行流程 唤醒后,会执行return Thread.interrupted();,这个函数返回的是当前执行线程的中断状态,并清除。
1
2
3
4
5
// java.util.concurrent.locks.AbstractQueuedSynchronizer
private final boolean parkAndCheckInterrupt () {
LockSupport . park ( this );
return Thread . interrupted ();
}
再回到acquireQueued代码,当parkAndCheckInterrupt返回True或者False的时候,interrupted的值不同,但都会执行下次循环。如果这个时候获取锁成功,就会把当前interrupted返回。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// java.util.concurrent.locks.AbstractQueuedSynchronizer
final boolean acquireQueued ( final Node node , int arg ) {
boolean failed = true ;
try {
boolean interrupted = false ;
for (;;) {
final Node p = node . predecessor ();
if ( p == head && tryAcquire ( arg )) {
setHead ( node );
p . next = null ; // help GC
failed = false ;
return interrupted ;
}
if ( shouldParkAfterFailedAcquire ( p , node ) && parkAndCheckInterrupt ())
interrupted = true ;
}
} finally {
if ( failed )
cancelAcquire ( node );
}
}
如果acquireQueued为True,就会执行selfInterrupt方法。
1
2
3
4
// java.util.concurrent.locks.AbstractQueuedSynchronizer
static void selfInterrupt () {
Thread . currentThread (). interrupt ();
}
该方法其实是为了中断线程。但为什么获取了锁以后还要中断线程呢?这部分属于Java提供的协作式中断知识内容,感兴趣同学可以查阅一下。这里简单介绍一下:
当中断线程被唤醒时,并不知道被唤醒的原因,可能是当前线程在等待中被中断,也可能是释放了锁以后被唤醒。因此我们通过Thread.interrupted()方法检查中断标记(该方法返回了当前线程的中断状态,并将当前线程的中断标识设置为False),并记录下来,如果发现该线程被中断过,就再中断一次。 线程在等待资源的过程中被唤醒,唤醒后还是会不断地去尝试获取锁,直到抢到锁为止。也就是说,在整个流程中,并不响应中断,只是记录中断记录。最后抢到锁返回了,那么如果被中断过的话,就需要补充一次中断。 AbstractQueuedSynchronizer总结 对于AbstractQueuedSynchronizer的分析,最核心的就是sync queue的分析。
每一个结点都是由前一个结点唤醒 当结点发现前驱结点是head并且尝试获取成功,则会轮到该线程运行。 condition queue中的结点向sync queue中转移是通过条件的signal()操作完成的。 当结点的状态为SIGNAL时,表示后面的结点需要运行。 参考文章 从ReentrantLock的实现看AQS的原理及应用
锁核心类AQS详解