深入分析 AQS 实现原理
一、什么是 AQS
AQS 全程为 AbstractQueuedSynchronizer,它提供了一个 FIFO 队列,可以看成是一个用来实现同步锁及其它涉及到同步功能的核心组件,常见的有,ReentrantLock、CountDownLatch等
AQS是一个抽象类,主要通过继承的方式来使用,它本身没有实现任何的同步接口,仅仅是定义了同步状态的获取以及师傅的方法来提高自定义的同步组件。
可以这么说,只要搞懂了 AQS,那么 J.U.C 中绝大部分的 API 都能轻松掌握。
1、AQS 的两种功能
从使用层面来讲,AQS 的功能分为两种:
- 独占锁:每次只能由一个线程持有锁。 ReentrantLock 就是以独占方式实现的互斥锁;
- 共享锁:允许多个线程同时获取锁,并发访问共享资源。比如 ReentrantReadWriteLock;
2、AQS 的内部实现
AQS 的实现依赖内部的 FIFO 的双向队列,如果当前线程竞争锁失败,那么AQS 会把当前线程以及等待状态信息构造成一个 Node 加入到这个队列重,同时调用 Unsafe 方法,使当前线程进入阻塞状态。当获取锁的线程释放了锁后,会从队列中唤醒下一个阻塞的节点(线程)
AQS 队列内部维护的是一个 FIFO 的双向链表,这种结构的特点是每个数据结构都有两个指针,分别指向直接的后继节点和直接前驱节点,所以双向链表可以从任意一个节点开始很方便的访问前驱和后继。线程抢占的方式是通过 CAS 操作修改state的值,修改成功意味着当前线程抢占到了资源,修改失败的线程则加入 FIFO 队列,等待被唤醒。
Node 类组成如下:
static final class Node { static final Node SHARED = new Node(); static final Node EXCLUSIVE = null; /**表面当前节点的线程 作废*/ static final int CANCELLED = 1; /**表明当前节点的下一节点的线程需要被 unparking 唤醒*/ static final int SIGNAL = -1; /**表面当前节点的线程 处于等待状态,也就是再等待队列,等待被通知*/ static final int CONDITION = -2;
<span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> PROPAGATE <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">3</span><span class="token punctuation">;</span> <span class="token keyword">volatile</span> <span class="token keyword">int</span> waitStatus<span class="token punctuation">;</span> <span class="token keyword">volatile</span> Node prev<span class="token punctuation">;</span> <span class="token comment">//前驱节点</span> <span class="token keyword">volatile</span> Node next<span class="token punctuation">;</span> <span class="token comment">//后继节点</span> <span class="token keyword">volatile</span> Thread thread<span class="token punctuation">;</span><span class="token comment">//当前线程</span> Node nextWaiter<span class="token punctuation">;</span> <span class="token comment">//存储在condition队列中的后继节点</span> <span class="token comment">// 是否为共享锁</span> <span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">isShared</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token keyword">return</span> nextWaiter <span class="token operator">==</span> SHARED<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">final</span> Node <span class="token function">predecessor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> NullPointerException <span class="token punctuation">{<!-- --></span> Node p <span class="token operator">=</span> prev<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>p <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">NullPointerException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">else</span> <span class="token keyword">return</span> p<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token function">Node</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span><span class="token comment">// Used to establish initial head or SHARED marker</span> <span class="token punctuation">}</span> <span class="token comment">// 将线程构造成一个Node,添加到等待队列</span> <span class="token function">Node</span><span class="token punctuation">(</span>Thread thread<span class="token punctuation">,</span> Node mode<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token comment">// Used by addWaiter</span> <span class="token keyword">this</span><span class="token punctuation">.</span>nextWaiter <span class="token operator">=</span> mode<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>thread <span class="token operator">=</span> thread<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">// 这个方法会在Condition队列使用</span> <span class="token function">Node</span><span class="token punctuation">(</span>Thread thread<span class="token punctuation">,</span> <span class="token keyword">int</span> waitStatus<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token comment">// Used by Condition</span> <span class="token keyword">this</span><span class="token punctuation">.</span>waitStatus <span class="token operator">=</span> waitStatus<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>thread <span class="token operator">=</span> thread<span class="token punctuation">;</span> <span class="token punctuation">}</span>
}
- 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
3、锁竞争、锁释放对队列的变化
3.1 锁竞争
当出现锁竞争时,这里会出现2种变化。
- 新的线程没有抢到锁,加入 FIFO 队列。
- 新的线程直接抢到了锁。
3.2 锁释放
当锁释放的时候,会判断 head节点的waitStatus的状态是不是 != 0,如果是,则说明 FIFO 队列有等待线程,唤醒Node1节点 保存的线程(thread1)。
- thread1抢到了锁,节点变化如下。
- thread1未抢到锁,这种情况就是有新的线程 thread3 先抢到了锁,那么节点是不变化的
二、AQS同步队列源码分析
清楚了AQS的基本架构以后,我们来分析一下 AQS 的源码,以 ReentrantLock 为模型。
2.1 抢占锁
ReentrantLock.lock
public void lock() {
sync.lock();
}
- 1
- 2
- 3
这个是获取锁的入口,调用sync这个类里面的方法,sync是什么呢?
abstract static class Sync extends AbstractQueuedSynchronizer
- 1
sync是一个静态内部类,它继承了 AQS 这个抽象类,前面说过 AQS 是一个同步工具,主要用来实现同步控制。我们在利用这个工具的时候,会继承它来实现同步控制功能。
通过进一步分析,发现 Sync 这个类有两个具体的实现,分别是 NofairSync(非公平锁),FailSync(公平锁)。
/** 公平锁 */
static final class FairSync extends Sync
/** 非公平锁 */
static final class NonfairSync extends Sync
- 1
- 2
- 3
- 4
- 5
- 公平锁 表示所有线程严格按照 FIFO 队列来获取锁
- 非公平锁 表示可以存在抢占锁的功能,也就是说不管当前队列上是否存在其他线程等待,新线程都有机会抢占锁
公平锁和非公平锁的实现上的差异,会在文章后面做一个分析,接下来的分析仍然以非公平锁作为主要分析逻辑。
NonfairSync.lock
final void lock() {
/**
通过 cas 操作来修改 state 状态,表示争抢锁的操作
*/
if (compareAndSetState(0, 1))
/**
获取锁成功
设置当前获得锁状态的线程,当前线程赋值给
AQS的exclusiveOwnerThread变量,目的为了实现重入
*/
setExclusiveOwnerThread(Thread.currentThread());
else
// 获取锁失败
acquire(1);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
由于 NonfairSync 继承了 Sync 继承了 AQS,其实是调用 AQS.compareAndSetState 方法
这段代码简单解释一下:
由于这里是非公平锁,所以调用 lock 方法时,先去通过 cas 去抢占锁
- 如果抢占锁成功,保存获得锁成功的当前线程
- 抢占锁失败,调用 acquire 方法来走锁竞争逻辑
AQS.compareAndSetState
对外提供,原子操作修改 AQS 的 state 状态方法。
protected final boolean compareAndSetState(int expect, int update) {
// 调用 CAS 的原子操作,修改 AQS 的 state状态
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
- 1
- 2
- 3
- 4
state
- 当 state=0 时,表示无锁状态
- 当 state>0 时,表示已经有线程获得了锁,也就是state=1,但是因为ReentrantLock允许重入,所以同一个线程多次获得同步锁的时候,state会递增,比如重入5次,那么state=5。 而在释放锁的时候,同样需要释放5次直到state=0其他线程才有资格获得锁
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> <span class="token keyword">private</span> <span class="token keyword">volatile</span> <span class="token keyword">int</span> state<span class="token punctuation">;</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
需要注意的是:不同的 AQS 实现,state 所表达的含义是不一样的。
Unsafe
Unsafe类是在 sun.misc 包下,不属于 Java 标准。但是很多 Java 的基础类库,包括一些被广泛使用的高性能开发库都是基于 Unsafe 类开发的,比如Netty、Hadoop、Kafka等;Unsafe 可认为是 Java 中留下的后门,提供了一些低层次操作,如直接内存访问、线程调度等
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
- 1
这个是一个 native 方法, 第一个参数为需要改变的对象,第二个为偏移量(即之前求出来的headOffset的值),第三个参数为期待的值,第四个为更新后的值
整个方法的作用是如果当前时刻的值等于预期值var4相等,则更新为新的期望值 var5,如果更新成功,则返回true,否则返回false;
AQS.acquire
如果 CAS 操作未能成功,说明 state 已经不为0,锁被其它线程持有,则走锁竞争的逻辑。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
- 1
- 2
- 3
- 4
- 5
这个方法的主要逻辑是:
- 通过 tryAcquire 尝试获取锁,如果成功返回 true,失败返回 false
- 如果 tryAcquire 失败,则会通过 addWaiter 方法将当前线程封装成 Node 添加到 AQS 同步队列尾部,注意这步只是加入,node 的 waitstatus 还没修改,线程也还没进入阻塞状态
- acquireQueued,将Node作为参数,通过自旋去尝试获取锁,获取失败则设置 Node 节点 waitstatus 的状态。
NonfairSync.tryAcquire
它是重写 AQS.tryAcquire 加锁逻辑方法,实现了 重试获取锁 + 非公平锁 + 可重入锁 特性。并且大家仔细看一下 AQS.tryAcquire 方法的定义,并没有实现,而是抛出异常。按照一般的思维模式,既然是一个不实现的模版方法,那应该定义成abstract,让子类来实现呀?大家想想为什么
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
- 1
- 2
- 3
Sync.nonfairTryAcquire
ffinal boolean nonfairTryAcquire(int acquires) {
//获得当前执行的线程
final Thread current = Thread.currentThread();
//获得state的值
int c = getState();
//state=0说明当前是无锁状态
if (c == 0) {
/**
通过cas操作来替换state的值改为1,大家想想为什么要用cas呢?
理由是,在多线程环境中,直接修改 state=1 会存在线程安全问题,你猜到了吗?
*/
if (compareAndSetState(0, acquires)) {
// 修改成功,抢到了锁资源, 保存当前获得锁的线程
setExclusiveOwnerThread(current);
return true;
}
}
//这段逻辑就很简单了。如果是同一个线程来获得锁,则直接增加重入次数,重入锁的实现
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires; //增加重入次数
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// 重新设置state = state + 1
setState(nextc);
return true;
}
return false;
}
- 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
该方法的主要逻辑:
- 获取当前线程,判断当前的锁的状态
- 如果 state = 0 表示当前是无锁状态,通过 cas 更新 state 状态的值,更新成功表面获取到了锁;更新失败继续往下执行。
- 如果当前线程是属于重入,则增加重入次数
AQS.addWaiter
当 tryAcquire 方法获取锁失败以后,则会先调用 addWaiter 将当前线程封装成Node,然后添加到 FIFQ 队列,只是添加到队列。
private Node addWaiter(Node mode) { //mode=Node.EXCLUSIVE 独占锁 //将当前线程封装成Node Node node = new Node(Thread.currentThread(), mode);
<span class="token comment">// tail是AQS的中表示同步队列队尾的属性,刚开始为 null,所以进行enq(node)方法</span> Node pred <span class="token operator">=</span> tail<span class="token punctuation">;</span> <span class="token comment">//tail不为空的情况</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>pred <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token comment">//将当前线程的 Node 的 prev 节点指向 tail</span> node<span class="token punctuation">.</span>prev <span class="token operator">=</span> pred<span class="token punctuation">;</span> <span class="token comment">// cas操作,将 AQS.tail 指向新的 node节点</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">compareAndSetTail</span><span class="token punctuation">(</span>pred<span class="token punctuation">,</span> node<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token comment">// 旧的tail 指向新的 tail节点</span> pred<span class="token punctuation">.</span>next <span class="token operator">=</span> node<span class="token punctuation">;</span> <span class="token comment">// 添加成功返回即可</span> <span class="token keyword">return</span> node<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token comment">/** 1.程序初期 tail = null 2.cas 添加新node 到队列尾失败 1.2 都进入这里 */</span> <span class="token comment">// 这里会保证最终成功添加到队列尾</span> <span class="token function">enq</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> node<span class="token punctuation">;</span>
}
- 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
该方法的主要逻辑是:
- 将当前线程封装成 Node对象
- 判断 FIFQ 节点中的 tail 是否为空 ,如果不为空,则通过 cas操作把当前线程的node添加到AQS队列
- 如果为空 或者 cas失败,调用 enq方法将节点添加到 AQS队列
AQS.enq
enq方法就是通过自旋操作把当前节点加入到队列中
private Node enq(final Node node) { // 自旋,不做过多解释,最终一定能成功添加到阻塞队列尾 for (;;) { Node t = tail; // 如果是第一次添加到队列,那么 tail == null if (t == null) { //创建一个空的Node,并通过CAS操作设置为head节点 if (compareAndSetHead(new Node())) //此时队列中只一个头结点,所以tail也指向它 tail = head;
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{<!-- --></span> <span class="token comment">/** tail 不为 null,进入else区域 仔细对比,else区逻辑和addWaiter方法添加到队列逻辑是一样的。这里会循环添加,直至添加成功。 */</span> <span class="token comment">// 将当前线程的 Node 的 prev 节点指向 tail</span> node<span class="token punctuation">.</span>prev <span class="token operator">=</span> t<span class="token punctuation">;</span> <span class="token comment">// cas操作,将 AQS.tail 指向新的 node节点</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">compareAndSetTail</span><span class="token punctuation">(</span>t<span class="token punctuation">,</span> node<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token comment">// 旧的tail 指向新的 tail节点</span> t<span class="token punctuation">.</span>next <span class="token operator">=</span> node<span class="token punctuation">;</span> <span class="token comment">// 添加成功返回即可</span> <span class="token keyword">return</span> t<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span>
}
- 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
通过 AQS.addWaiter 方法,即便有多个线程同时要插入 FIFQ 队列,最后都能插入成功。
AQS.acquireQueued
将添加到队列中的 Node 作为参数传入 acquireQueued 方法,这里面会再次做抢占锁的操作,抢占成功 or 失败都会有不同的操作,比如修改 note 节点的 waitstatus、线程阻塞、删除 node 节点等。
final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; // for循环,要么自旋抢占锁成功,要么线程挂起 for (;;) {
<span class="token comment">// 获取 node的 prev 节点,若为 null 即刻抛出 NullPointException</span> <span class="token keyword">final</span> Node p <span class="token operator">=</span> node<span class="token punctuation">.</span><span class="token function">predecessor</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">/** 1.如果 前驱prev 为 AQS.head 才有资格进行自旋抢锁 2.自旋抢锁 还是调用重新后的 tryAcquire 方法 */</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>p <span class="token operator">==</span> head <span class="token operator">&&</span> <span class="token function">tryAcquire</span><span class="token punctuation">(</span>arg<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token comment">/** 下面逻辑主要是:如果自旋抢锁成功, 当前节点往上升级为 head 节点 */</span> <span class="token function">setHead</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">;</span> p<span class="token punctuation">.</span>next <span class="token operator">=</span> null<span class="token punctuation">;</span> failed <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span> <span class="token comment">// 返回fasle,中断后面的逻辑处理</span> <span class="token keyword">return</span> interrupted<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/** 1.前驱prev 不是 head节点 2.自旋获取锁失败 1、2步骤都会走到这里,根据节点的 waitStatus 决定是否需要挂起线程 */</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">shouldParkAfterFailedAcquire</span><span class="token punctuation">(</span>p<span class="token punctuation">,</span> node<span class="token punctuation">)</span> <span class="token operator">&&</span> <span class="token function">parkAndCheckInterrupt</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// 若前面为true,则执行线程挂起,待下次唤醒的时候检测中断的标志</span> interrupted <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{<!-- --></span> <span class="token comment">/** 1.成功获取锁了,调用 return 会执行到这里,但是 faile = false 不会这些下面方法 2.抛出异常会执行到这里,哪里会抛出异常 tryAcquire() 和 predecessor(),会执行下面方法。主要是一种容错机制,因为你可以在业务种唤醒任何线程,为了不打断整个 AQS允许,需要这么一种措施 */</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>failed<span class="token punctuation">)</span> <span class="token comment">// 将 node 从同步队列种剔除</span> <span class="token function">cancelAcquire</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
}
- 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
该方法的逻辑如下:
- 获取当前节点 node 的 前驱prev节点
- 如果前驱prev节点 是 head节点,那么它就有资格去争抢锁,调用重写的 tryAcquire 抢占锁
- 抢占锁成功以后,把当前节点设置为 AQS.head,并且移除原来的初始化head节点
- 前驱prev 不是 head节点 or 自旋获取锁失败,根据 waitStatus 状态做相应的操作。对于刚加入同步队列的 node,那么它的前驱prev 的 waitStatus == 0,修改为 waitStatus = -1后,返回false再回到逻辑1。如果前驱prev 的waitStatus 已经是 -1了,则调用 LockSupport.park 进行线程挂起操作
这里会有一个问题,对于步骤4,如果 node的前驱prev一开始就是 head,修改waitStatus = -1后再自旋一次,无可厚非。但对于node的前驱prve第一次不是 head,再自旋一次有必要吗?
有必要的,第二次 node的前驱prve的节点 就可能升级为 head节点。
前面的逻辑都很好理解,主要看一下shouldParkAfterFailedAcquire
这个方法和parkAndCheckInterrupt
的作用
AQS.shouldParkAfterFailedAcquire
从上面的分析可以看出,node 的同步队列第二个节点自旋失败 or 不是第二个节点的则会进行调用 shouldParkAfterFailedAcquire(p, node)操作
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { // 节点的 waitStatus int ws = pred.waitStatus;
<span class="token comment">//如果是 SIGNAL = -1状态,返回 true,线程可以开始阻塞了</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>ws <span class="token operator">==</span> Node<span class="token punctuation">.</span>SIGNAL<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>ws <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token keyword">do</span> <span class="token punctuation">{<!-- --></span> node<span class="token punctuation">.</span>prev <span class="token operator">=</span> pred <span class="token operator">=</span> pred<span class="token punctuation">.</span>prev<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">while</span> <span class="token punctuation">(</span>pred<span class="token punctuation">.</span>waitStatus <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span> pred<span class="token punctuation">.</span>next <span class="token operator">=</span> node<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{<!-- --></span> <span class="token comment">/** 1.如果节点的waitStatus 为“0”或者“共享锁”状态,则设置节点为SIGNAL状态。 2.这里线程不会立刻进入阻塞状态,返回 false 后还会再重试一次自旋获取锁的机会 */</span> <span class="token function">compareAndSetWaitStatus</span><span class="token punctuation">(</span>pred<span class="token punctuation">,</span> ws<span class="token punctuation">,</span> Node<span class="token punctuation">.</span>SIGNAL<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
}
- 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
AQS.parkAndCheckInterrupt
如果shouldParkAfterFailedAcquire 返回了 true,则会执行:parkAndCheckInterrupt()方法,它是通过 LockSupport.park(this) 将当前线程挂起到 WATING状态(注意synchronized是进入Blocked状态),它需要等待一个中断、unpark方法来唤醒它,通过这样一种 FIFO 的机制的等待,来实现了 Lock的操作。
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
- 1
- 2
- 3
- 4
LockSupport
LockSupport 类是Java6引入的一个类,提供了基本的线程同步原语。LockSupport 实际上是调用了 Unsafe 类里的方法。在这里需要关注 Unsafe 的这2个方法:
public native void park(boolean var1, long var2);
public native void unpark(Object var1);
- 1
- 2
- 3
unpark 函数为线程提供“许可(permit)”,线程调用park函数则等待“许可”。这个有点像信号量,但是这个“许可”是不能叠加的,“许可”是一次性的。
permit 相当于0/1的开关,默认是0,调用一次 unpark 就加1变成了1。调用一次 park 会消费 permit,又会变成0。 如果再调用一次park会阻塞,因为permit已经是0了。直到permit变成1,这时调用unpark会把permit设置为1,每个线程都有一个相关的permit,permit最多只有一个,重复调用unpark不会累积
如下一些案例,稍微演示一下,会更容易理解:
public static void main(String[] args) { ystem.out.println(1); // 先唤醒,设置 peimit = 1 LockSupport.unpark(Thread.currentThread()); // 阻塞, peimit = 0,peimit 可消费,线程不阻塞继续往下执行 LockSupport.park();
System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
}
result:
1
2
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
这里可以看出,park 和 unpark 执行顺序是没影响的。
public static void main(String[] args) {
ystem.out.println(1);
// 先唤醒,设置 peimit = 1
LockSupport.unpark(Thread.currentThread());
// 阻塞, peimit = 0,peimit 可消费,线程不阻塞继续往下执行
LockSupport.park();
// 阻塞, peimit = -1,线程阻塞,不往下执行
LockSupport.park();
System.out.println(2);
}
result:
1
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
public static void main(String[] args) {
ystem.out.println(1);
// 先唤醒,设置 peimit = 1
LockSupport.unpark(Thread.currentThread());
// 先唤醒,设置 peimit = 1,并不会自增的
LockSupport.unpark(Thread.currentThread());
// 阻塞, peimit = 0,peimit 可消费,线程不阻塞继续往下执行
LockSupport.park();
// 阻塞, peimit = -1,线程阻塞,不往下执行
LockSupport.park();
System.out.println(2);
}
result:
1
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
2.2 释放锁
ReentrantLock.unlock
该方法主要调用了 AQS.release(int arg) 方法
public void unlock() {
sync.release(1);
}
- 1
- 2
- 3
AQS.release
AQS.tryRelease 释放锁逻辑方法,里面会抛出异常,它和 AQS.tryAcquire 加锁逻辑方法一样,需要被重写;ReentrantLock.NonfairSync 实现 AQS.tryAcquire 方法有重入锁的逻辑,它同样要实现 AQS.tryRelease 方法也要有重入锁释放的逻辑
public final boolean release(int arg) {
// tryRelease 试图去释放锁
if (tryRelease(arg)) {
Node h = head;
// 判断 FIFQ 队列中是否有 waiting的线程
if (h != null && h.waitStatus != 0)
// 进入唤醒的逻辑
unparkSuccessor(h);
return true;
}
return false;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
该方法主要做了两件事:
- 释放锁
- 如果同步队列有 waiting 的线程,则唤醒挂起的线程
Sync.tryRelease
protected final boolean tryRelease(int releases) { int c = getState() - releases; // 检验 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false;
<span class="token comment">/** 1.如果 c = 0,线程释放锁, 2.如果 c != 0,线程不释放锁,state - 1,这里就是重入锁的判断 */</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>c <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> free <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token function">setExclusiveOwnerThread</span><span class="token punctuation">(</span>null<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token function">setState</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> free<span class="token punctuation">;</span> <span class="token punctuation">}</span>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
AQS.unparkSuccessor
在释放锁后,如果 FIFQ 队列中还有waiting线程,则需要唤醒。
private void unparkSuccessor(Node node) { // 获取 当前节点 也就是 head 节点的 waitStatus int ws = node.waitStatus;
<span class="token keyword">if</span> <span class="token punctuation">(</span>ws <span class="token operator"><</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token comment">// 小于0,表示后驱节点的线程阻塞,cas操作修改 当前节点 Node waitStatus = 0</span> <span class="token function">compareAndSetWaitStatus</span><span class="token punctuation">(</span>node<span class="token punctuation">,</span> ws<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 获取当前节点node 的后驱节点</span> Node s <span class="token operator">=</span> node<span class="token punctuation">.</span>next<span class="token punctuation">;</span> <span class="token comment">// 判断head的后继节点是否为空 或者 是否是取消状态,</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>s <span class="token operator">==</span> null <span class="token operator">||</span> s<span class="token punctuation">.</span>waitStatus <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> s <span class="token operator">=</span> null<span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token punctuation">(</span>Node t <span class="token operator">=</span> tail<span class="token punctuation">;</span> t <span class="token operator">!=</span> null <span class="token operator">&&</span> t <span class="token operator">!=</span> node<span class="token punctuation">;</span> t <span class="token operator">=</span> t<span class="token punctuation">.</span>prev<span class="token punctuation">)</span> <span class="token comment">// 从尾反向遍历,主要是因为head后驱节点是 null or 是取消状态,我们应该唤醒head后驱节点的下一个节点,那如果直接 s.next获取就可以了不是吗?错了,在doAcquireInterruptibly.cancelAcquire方法,它将 node.next = node 设置为自己 </span> <span class="token keyword">if</span> <span class="token punctuation">(</span>t<span class="token punctuation">.</span>waitStatus <span class="token operator"><=</span> <span class="token number">0</span><span class="token punctuation">)</span> s <span class="token operator">=</span> t<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/** 如果获取到的节点不为空,则直接通过:“LockSupport.unpark()”方法 来释放对应的被挂起的线程, 这样一来将会有一个节点唤醒后继续进入循环进一步尝试 tryAcquire()方法来获取锁 */</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>s <span class="token operator">!=</span> null<span class="token punctuation">)</span> LockSupport<span class="token punctuation">.</span><span class="token function">unpark</span><span class="token punctuation">(</span>s<span class="token punctuation">.</span>thread<span class="token punctuation">)</span><span class="token punctuation">;</span>
}
- 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
三、AQS 等待队列源码分析
AQS 的内部类 ConditionObject 主要是为并发编程中的同步提供了等待通知的实现方式,可以在不满足某个条件的时候挂起线程等待。直到满足某个条件的时候在唤醒线程。
这里也以 ReentrantLock 为模型,使用方法如下:
public class AQSTest {
<span class="token keyword">public</span> <span class="token keyword">static</span> ReentrantLock lock <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ReentrantLock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> Condition condition <span class="token operator">=</span> lock<span class="token punctuation">.</span><span class="token function">newCondition</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 消费者</span> <span class="token keyword">public</span> <span class="token keyword">static</span> Thread thread <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> lock<span class="token punctuation">.</span><span class="token function">lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">try</span> <span class="token punctuation">{<!-- --></span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">" 进入瓜地等待西瓜成熟"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> condition<span class="token punctuation">.</span><span class="token function">await</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">" 被唤醒去摘西瓜"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">InterruptedException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">" 摘了西瓜"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> lock<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token comment">// 生产者</span> <span class="token keyword">public</span> <span class="token keyword">static</span> Thread thread1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> lock<span class="token punctuation">.</span><span class="token function">lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> condition<span class="token punctuation">.</span><span class="token function">signal</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">" 瓜地生产了西瓜"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> lock<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> thread<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">try</span> <span class="token punctuation">{<!-- --></span> Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">100</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">InterruptedException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> thread1<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
}
- 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
以上生产者、消费者模式是不是很眼熟。对,Condition 主要是为了配合 ReentrantLock 使用,类似于 wait 和 notify 配合 synchronize 使用一样,起到线程之间的通信作用。只不过 Condition 作用更为强大,在一个 AQS同步器中,可以定义多个 Condition。
AQS 的内部类 ConditionObject 主要提供了这三个方法来实现,线程之间的通信:
// 当前线程添加到 等待队列尾,释放锁,然后进入 waiting状态
public final void await() throws InterruptedException{};
public final void signalAll(){
};
// 讲等待队列第一个节点,加入到 同步队列,等待被唤醒
public final void signal(){
};
- 1
- 2
- 3
- 4
- 5
- 6
等待队列也是一个 FIFO 的队列,跟同步队列一样,节点也是 AbstractQueuedSynchronizer.Node类。
3.1 进入等待队列,释放锁资源
AQS$ConditionObject.await
AQS内部类ConditionObject提供的等待方法await
public final void await() throws InterruptedException { // 判断当前线程是否设置中断标识 if (Thread.interrupted()) throw new InterruptedException(); // 生成Node节点,并插入到 FIFO 等待队列尾 Node node = addConditionWaiter();
<span class="token comment">/** 由于调用await()方法的线程是已经获取了锁的,所以在加入到等待队列之后, 需要去释放锁,并唤醒同步队列下一个阻塞线程 */</span> <span class="token keyword">int</span> savedState <span class="token operator">=</span> <span class="token function">fullyRelease</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">int</span> interruptMode <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token comment">/** 判断当前node 在不在同步队列,如果不在进行线程阻塞 如果在的话,说明在释放锁后,被其它线程获取锁执行了 signal 方法, 那么线程没必须进入阻塞状态了(这里要结合释放锁的代码看) */</span> <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">isOnSyncQueue</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token comment">// 当前线程挂起</span> LockSupport<span class="token punctuation">.</span><span class="token function">park</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 当这个线程被唤醒后,会继续往下执行</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>interruptMode <span class="token operator">=</span> <span class="token function">checkInterruptWhileWaiting</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">// 熟悉的 acquireQueued 方法,上文有解析 </span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">acquireQueued</span><span class="token punctuation">(</span>node<span class="token punctuation">,</span> savedState<span class="token punctuation">)</span> <span class="token operator">&&</span> interruptMode <span class="token operator">!=</span> THROW_IE<span class="token punctuation">)</span> interruptMode <span class="token operator">=</span> REINTERRUPT<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>node<span class="token punctuation">.</span>nextWaiter <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token comment">// clean up if cancelled</span> <span class="token function">unlinkCancelledWaiters</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>interruptMode <span class="token operator">!=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token function">reportInterruptAfterWait</span><span class="token punctuation">(</span>interruptMode<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
- 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
这里的逻辑一定是先添加到等待队列,再释放锁资源,再看线程要不要阻塞挂起
这个方法的主要逻辑:
- 将当前线程封装成 node,添加到 FIFO 等待队列尾
- 释放当前线程的锁资源
- 判断线程是否要挂起等待被唤醒
- 线程被唤醒执行 acquireQueued 获取锁的方法
AQS$ConditionObject.addConditionWaiter
该方法主要是生成 Node节点,并插入到 FIFO 等待队列尾
private Node addConditionWaiter() {
Node t = lastWaiter;
// 如果尾节点 != null 且 尾节点 不是 条件等待状态
if (t != null && t.waitStatus != Node.CONDITION) {
// 从头节点开始清除 不是 条件等待状态的节点
unlinkCancelledWaiters();
// 重新给t 赋值 新的尾节点
t = lastWaiter;
}
// 将当前线程 封装成 node
Node node = new Node(Thread.currentThread(), Node.CONDITION);
// 将 node插入等待队列队尾
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
AQS.fullyRelease
该方法获取当前 state 的值,调用 AQS.release,释放锁资源,唤醒同步队列的下一个节点线程
final int fullyRelease(Node node) { boolean failed = true; try { // 获取state的值 int savedState = getState(); // 这里会调用AQS.release,上面有讲过,释放锁资源,唤醒同步队列的下一个节点线程 if (release(savedState)) { failed = false;
<span class="token keyword">return</span> savedState<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{<!-- --></span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalMonitorStateException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{<!-- --></span> <span class="token keyword">if</span> <span class="token punctuation">(</span>failed<span class="token punctuation">)</span> node<span class="token punctuation">.</span>waitStatus <span class="token operator">=</span> Node<span class="token punctuation">.</span>CANCELLED<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
3.2 唤醒线程
AQS$ConditionObject.signal
AQS内部类 ConditionObject 提供的通知方法 signal
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
// 取等待队列 第一个节点进行唤醒
doSignal(first);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
AQS$ConditionObject.doSignal
private void doSignal(Node first) { do { // 这部分代码是将第一个 node节点 从等待队列剔除 if ( (firstWaiter = first.nextWaiter) == null) lastWaiter = null; first.nextWaiter = null;
<span class="token comment">// 从first开始遍历等待队列,把第一个非空、不是等待状态的node节点transfer到同步队列</span> <span class="token punctuation">}</span> <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">transferForSignal</span><span class="token punctuation">(</span>first<span class="token punctuation">)</span> <span class="token operator">&&</span> <span class="token punctuation">(</span>first <span class="token operator">=</span> firstWaiter<span class="token punctuation">)</span> <span class="token operator">!=</span> null<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
该方法的主要逻辑:
- 剔除等待队列 的第一个节点
- 将第一个节点转移到 同步队列
AQS.transferForSignal
final boolean transferForSignal(Node node) {
<span class="token comment">// CAS操作 将节点 从 -2等待状态 改为 0无锁状态</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">compareAndSetWaitStatus</span><span class="token punctuation">(</span>node<span class="token punctuation">,</span> Node<span class="token punctuation">.</span>CONDITION<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span> <span class="token comment">// 这里调用AQS.enq方法,之前有讲过,通过自旋操作把当前节点加入到队列中。</span> <span class="token comment">// 这里返回的p:同步队列中,node节点的 前驱prev节点。</span> Node p <span class="token operator">=</span> <span class="token function">enq</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">int</span> ws <span class="token operator">=</span> p<span class="token punctuation">.</span>waitStatus<span class="token punctuation">;</span> <span class="token comment">// ws > 0,如果node的前驱节点是取消状态,直接唤醒 node的等待线程</span> <span class="token comment">// 如果 node的前驱节点不是取消状态,则修改前驱节点的状态为 等待触发状态-1,不进行唤醒。</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>ws <span class="token operator">></span> <span class="token number">0</span> <span class="token operator">||</span> <span class="token operator">!</span><span class="token function">compareAndSetWaitStatus</span><span class="token punctuation">(</span>p<span class="token punctuation">,</span> ws<span class="token punctuation">,</span> Node<span class="token punctuation">.</span>SIGNAL<span class="token punctuation">)</span><span class="token punctuation">)</span> LockSupport<span class="token punctuation">.</span><span class="token function">unpark</span><span class="token punctuation">(</span>node<span class="token punctuation">.</span>thread<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
该方法的主要逻辑:
- 把等待队列剔除的 node节点 状态从 -2 修改为 0
- 将 node节点 添加到 同步队列
- 判断 node的前驱节点是否取消状态,如果是,直接唤醒 node的等待线程;如果不是,修改 node的前驱节点为等待触发状态-1。
这里有个问题,等待队列剔除的node,添加到同步队列后,怎么唤醒呢?
结合上文讲的同步队列,在调用 AQS.release 后会释放锁资源,唤醒同步队列下一个阻塞线程,也就是在调用 ReentrantLock.unlock 时。
最后附上一张流程图: