源码分析AbstractQuenedSynchronized(一)
源码分析AbstractQuenedSynchronized(三)
主要分析AbstractQuenedSynchronized的Condition接口实现类ConditionObject,包括await方法和signal方法
Condition
Condition的产生通过ReentrantLock实例的newCondition方法,而实现ReentrantLock功能的内部类Sync又是AbstractQuenedSynchronized的实现类,由此可以看出:Condition依赖于ReentrantLock实现,而ReentrantLock又依赖于AbstractQuenedSynchronized实现.
//##ReentrantLock类
final ConditionObject newCondition() {
//实例化一个ConditionObject
return new ConditionObject();
}
//##AbstractQuenedSynchronized类
public class ConditionObject implements Condition, java.io.Serializable {
/** 条件队列的第一个结点 */
private transient Node firstWaiter;
/** 条件队列的最后一个结点 */
private transient Node lastWaiter;
public ConditionObject() { }
}
由Condition的实现类ConditionObject我们可以看出由两个属性:firstWaiter和lastWaiter,它们都是Node。
在上一篇介绍 AQS 的时候,我们有一个阻塞队列(sync queue),用于保存等待获取锁的线程的队列。这里我们引入另一个概念,叫条件队列(condition queue),如下图所示。
图片源于https://javadoop.com/post/AbstractQueuedSynchronizer-2
从图中,我们首先需要了解的是:
- 条件队列和阻塞队列的结点都是Node类型,因为条件队列的结点是要转移到阻塞队列中的
- Condition的实例是通过ReentrantLock的newCondition方法产生的,一个newCondition产生一个新的Condition实例
- 每一个Condition实例都有一个关联的条件队列,由上面的ConditionObject 类我们也可以看出,只有两个属性:条件队列的头结点和末尾结点。如线程 1 调用 condition1.await() 方法即可将当前线程 1 包装成 Node 后加入到条件队列中,然后阻塞在这里,不继续往下执行,条件队列是一个单向链表
- 调用condition1.signal() 触发一次唤醒,此时唤醒的是队头,会将condition1 对应的条件队列的 firstWaiter(队头) 移到阻塞队列的队尾,等待获取锁,获取锁后 await 方法才能返回,继续往下执行。
await方法
//##############AbstractQuenedSynchronized类中的ConditionObject类########################
public final void await() throws InterruptedException {
//1.首先判断线程状态是否中断
if (Thread.interrupted())
//中断则直接抛异常
throw new InterruptedException();
//2.将当前线程封装成状态为CONDITION的Node并添加到条件队列队尾
Node node = addConditionWaiter();
/*======================================================================
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
// 将队列中状态不为CONDITION的结点移除
unlinkCancelledWaiters();
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
======================================================================*/
//3.调用await方法之前,当前线程必须要持有锁,然后释放锁,这里要完全释放独占锁
int savedState = fullyRelease(node);//savedState为释放锁之前的state
/*======================================================================
final int fullyRelease(Node node) {
boolean failed = true;
try {
int savedState = getState();//返回的savedState为释放锁之前的state
//释放锁
//如果一个线程没有获得锁(没有执行lock方法)就执行condition.await()方法,该线程结点会进入等待队列然后执行release方法抛出异常,同时node状态变为cancelled,这个已经入队的结点在后面会被后续结点“请出去”
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
//当释放锁失败时
node.waitStatus = Node.CANCELLED;
}
}
======================================================================*/
int interruptMode = 0;
//4. 当线程结点不在阻塞队列时将线程挂起
/*退出当前循环有两种情况:一、当前线程node转移到阻塞队列中 二、线程中断*/
while (!isOnSyncQueue(node))
/*======================================================================
// 当一个node从条件队列转移到阻塞队列时返回true
final boolean isOnSyncQueue(Node node) {
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
//有后继结点,说明一定在阻塞队列
if (node.next != null) // If has successor, it must be on queue
return true;
//不能通过node.prev!=null来判断node在阻塞队列,因为CAS操作将自己设为新的tail过程中可能会失败,因此该方法findNodeFromTail从tail往前遍历看能不能在阻塞队列中找到该node
return findNodeFromTail(node);
}
======================================================================*/
{
//当前线程结点不在阻塞队列中,当前线程挂起
LockSupport.park(this);
/*========================
5 有以下三种情况会让 LockSupport.park(this); 这句返回继续往下执行:
(1)常规路径。signal -> 转移节点到阻塞队列 -> 获取了锁(unpark)
(2)线程中断。在 park 的时候,另外一个线程对这个线程进行了中断
(3)signal 的时候我们说过,转移以后的前驱节点取消了,或者对前驱节点的CAS操作失败了
==========================*/
//逻辑走到这里,说明线程被唤醒,检查中断状态
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
/*======================================================================
interruptMode 可以取值为 REINTERRUPT(1),THROW_IE(-1),0
// 1. 如果在 signal 之前已经中断,返回 THROW_IE
// 2. 如果是 signal 之后中断,返回 REINTERRUPT
// 3. 没有发生中断,返回 0
private int checkInterruptWhileWaiting(Node node) {
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
}
final boolean transferAfterCancelledWait(Node node) {
//由于Signal操作将node的WaitStatus设为0,所以如果下面CAS成功则说明中断发生在signal方法前;CAS失败说明发生在signal方法后
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
//即使中断,也会转移到阻塞队列
//这里描绘了一个场景,本来有个线程,它是排在条件队列的后面的,但是因为它被中断了,那么它会被唤醒,然后它发现自己不是被 signal 的那个,但是它会自己主动去进入到阻塞队列。
enq(node);
return true;
}
//逻辑走到这里说明中断发生在signal方法后,但是signal方法可能还没有结束,因此这边自旋等待其完成
while (!isOnSyncQueue(node))
Thread.yield();
return false;
}
======================================================================*/
//6.被唤醒后当前线程node进入阻塞队列,等待获取锁
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
//acquireQueued(node, savedState) 的返回值就是代表线程是否被中断。逻辑到这里,说明线程在signal方法后发生中断
interruptMode = REINTERRUPT;//将状态设为REINTERRUPT,用于待会重新中断
if (node.nextWaiter != null) // clean up if cancelled
//signal方法会将结点从条件队列转移到阻塞队列,同时断开node和后面结点的联系,逻辑走到这里,说明signal之前发生了中断,也需要将节点进行转移到阻塞队列,这部分转移的时候,是没有设置 node.nextWaiter = null 的。
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
/*======================================================================
private void reportInterruptAfterWait(int interruptMode)
throws InterruptedException {
//THROW_IE则抛异常,REINTERRUPT则重新中断(这里中断的含义是设置一个线程的中断状态为true,并没有抛异常!)
if (interruptMode == THROW_IE)
throw new InterruptedException();
else if (interruptMode == REINTERRUPT)
selfInterrupt();// Thread.currentThread().interrupt();
}
======================================================================*/
}
注意到上述await方法的步骤5有三种情况会让 LockSupport.park(this)方法返回,除了响应中断就是利用正常途径signal方法了,下面开始分析Signal方法
signal方法
//##承接await方法的步骤4:LockSupport.park(this);
//唤醒等待最久的线程,将这个线程node从条件队列中转移到阻塞队列中
public final void signal() {
// 调用 signal 方法的线程必须持有当前的独占锁
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
/*======================================================================
private void doSignal(Node first) {
do {
//将firstWaiter 指向下一个Waiter,因为它就要转移到阻塞队列了
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
//first即将离开,断开和后面的关系
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);//这里 while 循环,如果 first 转移不成功,那么选择 first 后面的第一个节点进行转移,依此类推
}
//返回return代表:成功将node转移到阻塞队列 false:该节点已经取消,不需要转移了
final boolean transferForSignal(Node node) {
// CAS 如果失败,说明此 node 的 waitStatus 已不是 Node.CONDITION,说明节点已经取消,
// 既然已经取消,也就不需要转移了,方法返回,转移后面一个节点
// 否则,将 waitStatus 置为 0
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
Node p = enq(node);//自旋进入阻塞队列的队尾,p为node在阻塞队列的前驱结点
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
//正常情况下不会进入该逻辑,但是如果前驱结点取消或者CAS设置前驱结点为SINGAL失败则唤醒当前线程,直接进入步骤4的下一步
LockSupport.unpark(node.thread);
return true;
}
======================================================================*/
总结
Conditiond的await和signal过程:
await
1.首先判断线程状态是否中断
2.将当前线程封装成状态为CONDITION的Node并添加到条件队列队尾
3.释放锁,这里要完全释放独占锁(调用await方法之前,当前线程必须要持有锁,否则在尝试释放锁时就会抛异常)
4. 当线程结点不在阻塞队列时将线程挂起
5.signal方法将这个线程node从条件队列中转移到阻塞队列中(转移到阻塞队列的方式有两个:signal方式和中断方式)
6.在阻塞队列中等待重新获取锁