前言
在Lock出现之前,我们使用对多的同步方式就是synchronized,配合Object的wait、notify来实现等待、通知机制。 Condition接口也提供了类似的方法,与lock配合使用。
介绍
Object的监视器方法:wait、notify、notifyAll,与Synchronized配合使用,必须先获得锁,才能执行监视器方法
Condition方法:await、signal、signalAll,与Lock配合使用,必须先获取Lock锁,才能执行Condition方法。
源码分析:
基本结构
通过UML类图可以看出,Condtion是一个接口,它的实现时在AbrtractQueuedSynchronizer的内部类ConditionObject实现的。下面主要从await和signal两个方法入手,从源码了解一下ContionObject.
ContionObject参数
/** First node of condition queue. */
private transient Node firstWaiter; // 等待队列首节点
/** Last node of condition queue. */
private transient Node lastWaiter; // 等待队列尾结点
await 方法
await会让当前线程挂起,直到别的线程打断,或者获取到锁的线程调用signal方法。
在await方法上挂起的线程,只有在以下情况才会唤醒:
- 被别的线程中断
- 其它获取锁的线程调用signal,且当前线程的节点处在首节点
- 其它获取锁的线程调用signalAll
- 发生虚假唤醒
在此方法返回之前,此线程必须重新获取到锁。
现在来看AQS内部的实现逻辑:
public final void await() throws InterruptedException {
if (Thread.interrupted()) // 检查是否中断
throw new InterruptedException();
Node node = addConditionWaiter(); // 添加节点到条件队列尾部
int savedState = fullyRelease(node); // 完全释放锁,并把重入次数保存
int interruptMode = 0;
while (!isOnSyncQueue(node)) { // 自旋判断该节点是否加入到同步队列
LockSupport.park(this); // 如果不在同步队列,就挂起
/*
checkInterruptWhileWaiting 检查中断
1 未发生中断返回0
2 在调用signal/siganlall之前发生中断,返回THROW_IE(-1),并自己尝试加入到等待队列
3 在调用signal/siganlall之后发生中断,返回REINTERRUPT(1)
如果返回THROW_IE 在后面会抛出InterruptedException,如果返回REINTERRUPT 在后面会补中断信号
*
*/
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
/*
加入到同步队列的node,在acquireQueued中重新获取资源,如果在while中没有产生中断,在acquireQueued
产生了中断,acquireQueued会返回true,仍把interruptMode=REINTERRUPT
*/
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
/*
正常通过 singal/singalAll转移到同步队列的节点,node.nextWaiter为null
如果线程产生中断THROW_IE或者fullyRelease 出现错误,node.nextWaiter 不为null
那么需要清理点这些节点
*/
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters(); // 清理已经取消的节点
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode); // 如果interruptMode 为-1 抛出InterruptedException,如果为1 重新设置中断标识
}
await方法执行逻辑:
- 添加Node.CONDITION类型的节点,并加入到条件队列的尾部
- 完全释放锁,并保存锁的状态
- 判断有没有在同步队列,如果不在同队队列就挂起
- 从挂起处唤醒之后,检查是否发生了中断
- 从while循环跳出,在同步队列,准备获取锁 在介绍await执行流程的时候,提到了两个队列:
- ContinObject的队列,也叫条件队列
- AQS的队列,同步队列
addConditionWaiter
private Node addConditionWaiter() {
Node t = lastWaiter; // 队列尾结点
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) { // 如果尾结点已经取消,则清理掉所有取消的节点
unlinkCancelledWaiters(); // 清理所有已经取消的节点
t = lastWaiter; // 从新获取尾结点
}
Node node = new Node(Thread.currentThread(), Node.CONDITION); // 创建一个Node.CONDITION类型的节点
if (t == null) // 如果是第一次添加
firstWaiter = node;
else
t.nextWaiter = node; // 挂到当前尾结点的后面
lastWaiter = node; // 让lastWaiter指向自己
return node;
}
从addConditionWaiter方法可以看出,只是创建了一个类型为Node.CONDITION的节点。同时通过代码也可以看出:
- 条件队列只用到了Node中的Thread、waitStatus、nextWaiter
- 条件队列是单向队列
AQS队列和Contion队列的对比:
AQS队列:
Contion队列:
waitStatus可能的几种状态说明:
- 默认是0
- 如果大于0,说明该节点超时或者中断,需要从队列中移除
- 如果等于-1,说明后继节点等待被唤醒
- 如果等于-2,说明在条件队列中
- 如果等于-3,说明唤醒状态需要向下传播,共享锁的时候会用到
fullyRelease(Node node)
final int fullyRelease(Node node) {
boolean failed = true;
try {
int savedState = getState(); // 获取状态
if (release(savedState)) { // 释放锁,不管是重入几次,一次性释放所有的
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED; // 释放锁过程中发生异常,把当前节点设置成取消
}
}
通过上面的分析我们已经清楚了,当获取到锁的线程调用await方法,会创建一个Node节点,并加入到条件队列的尾部,然后释放所持有的线程。如果不在同步队列中会被park,直到有别的线程signal。
isOnSyncQueue
final boolean isOnSyncQueue(Node node) {
if (node.waitStatus == Node.CONDITION || node.prev == null) // 节点状态是Node.CONDITION或者前驱是null,一定是在条件队列中
return false;
if (node.next != null) // node.next不是null,一定是在同步队列中,反之则不一定,因为cpu分片执行,在某个瞬间会存在没有后继的情况
return true;
return findNodeFromTail(node); // 从尾部遍历一遍
}
private boolean findNodeFromTail(Node node) {
Node t = tail;
for (;;) {
if (t == node)
return true;
if (t == null)
return false;
t = t.prev;
}
}
执行到以上代码,说明当前线程已经成功入队,并被park了,如果从park中返回,会检查中断,上面已经分析过了,然后到acquireQueued方法:
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)) { // 如果是head且尝试获取锁成功,tryAcquire 模板方法,有具体子类实现
setHead(node); // 设置head,同一时刻只有一个线程获取锁成功,这里setHead 是线程安全的,且一定能成功
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node); // 取消获取
}
}
当节点进入到同步队列,会尝试获取资源,同时设置 savedState 的值,这个值则是代表当初释放锁的时候释放了多少重入次数。
整个执行的流程:
public final void signal() {
if (!isHeldExclusively()) // 判断当前线程是否是获取独占锁的线程
throw new IllegalMonitorStateException();
Node first = firstWaiter; // 从首节点开始唤醒
if (first != null)
doSignal(first);
}
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null) // 如果首节点没有后继节点
lastWaiter = null; // 尾节点指向null
first.nextWaiter = null; // 释放当前节点
} while (!transferForSignal(first) && // 如果当前first节点已经取消且新的首节点不是null,则继续循环唤醒
(first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) // cas失败,说明节点已经取消
return false;
Node p = enq(node); // 加入同步队列,并返回前驱节点
int ws = p.waitStatus; // 前驱节点的状态
/**
* 如果前驱状态大于0(取消),或者设置前驱节点状态失败(设置前驱节点为 Node.SIGNAL的目的是告诉前驱节点,释放锁之前记得唤醒我),就唤醒当前节点,保证同步队列的健壮性
*/
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}