Condition
Java5没有Lock之前,我们使用synchronized来控制同步,使用Object上的wait()、wait(long timeout)、notify()、notifyAll()方法二者进行配合使用,实现等待/通知模式。
Java5之后出现了Condition接口,也提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式。
对比项 | Object Monitor Methods | Condition |
---|---|---|
前置条件 | 获取对象的锁 | 调用Lock.lock()获得锁 调用Lock.newCondition()获取Condition对象 |
调用方式 | Object.wait() | Condition.await() |
等待队列格式 | 一个 | 多个 |
当前线程释放锁并进入等待状态 | 支持 | 支持 |
当前线程释放锁并进入等待,在等待过程中不响应中断 | 不支持 | 支持 |
当前线程释放锁并进入超时等待 | 支持 | 支持 |
当前线程释放锁并等待至某个时间点 | 不支持 | 支持 |
唤醒等待队列中的一个线程 | 支持 | 支持 |
唤醒等待队列中的所有线程 | 支持 | 支持 |
基本方法
- await():使当前线程在接收到信号或者被中断之前一直处于等待状态
- await(long time,Time unit):使当前线程在接收到信号、被中断或到达等待时间之前一直处于等待状态
- long awaitNanos(long nanosTimeout):使当前线程在接收到信号、被中断或到达等待时间之前一直处于等待状态。返回值表示剩余时间,如果在nanosTimeout之前被唤醒,那么返回值就是nanosTimeout-消耗的时间;如果返回值小于0就说明已经超时了
- awaitUninterruptibly():使当前线程在接收到信号之前一直处于等待状态,对中断不敏感
- 当前线程进入等待状态直到被通知、中断或者到达指定的时间。如果没有到达指定时间就被唤醒,返回true,否则返回false
- void signle():唤醒一个等待在Condition上的线程,该线程从等待方法返回前必须获得与Condition相关的锁
- void signalAll():唤醒所有等待在Condition上的线程,该线程从等待方法返回前必须获得与Condition相关的锁
Condition实现分析
ConditionObject是AQS的内部类,因为Condition的操作需要获取相关联的锁,,因此作为同步器的内部类也很合理。
每个Condition对象都包含着一个等待队列,是Condition对象实现等待/通知的功能的关键。
等待队列
等待队列是一个FIFO的队列,队列中的每一个节点都包含了一个线程引用,该线程引用就是Condition对象上等待的线程,如果一个线程调用了await()或者await开头的方法,那么该线程将会释放锁、构造节点加入等待队列中并进入等待状态。等待队列的节点复用了AQS中的同步队列的节点内部类Node。
public class ConditionObject implements Condition, java.io.Serializable {
private static final long serialVersionUID = 1173984872572414699L;
/** First node of condition queue. */
//等待队列中的头节点
private transient Node firstWaiter;
/** Last node of condition queue. */
//等待队列中的尾节点
private transient Node lastWaiter;
/**省略**/
复制代码
如图所示,Condition拥有首尾节点的引用,新增节点只需要将尾节点的next指向新增节点,并更新尾节点的引用即可。更新和插入并没有使用CAS,因为调用await方法的线程一定获取到了锁对象,是线程安全的。
等待
调用Condition的await开头的方法会使当前线程进入等待队列并释放锁,当从await()方法返回时,当前线程一定是获取了Condition相关的锁。
await()
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);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) {
//如果已经中断了,则退出
break;
}
}
//自旋获得同步状态/锁
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
//清理等待队列中不是在等待条件的节点
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
复制代码
此段代码逻辑为:
addConditionWaiter
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
//判断等待队列的尾节点是否不是CONDITION状态,那么就清除
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
//尾节点为空,直接把当前节点当作尾节点
firstWaiter = node;
else
t.nextWaiter = node;//否则将尾节点的next指向当前节点
lastWaiter = node;//把当前节点的引用赋给尾节点
return node;
}
复制代码
通知
调用Condition的signal方法将会唤醒在等待队列中等待时间最长的节点(头节点),在唤醒之前会先把它移到同步队列中去。
signal
public final void signal() {
if (!isHeldExclusively())//判断当前线程是否已经获得锁了
throw new IllegalMonitorStateException();
//得到头节点
Node first = firstWaiter;
if (first != null)
//唤醒头节点
doSignal(first);
}
复制代码
doSignal
private void doSignal(Node first) {
do {
//修改头节点
if ((firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
复制代码
transferForSignal
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
*/
//CAS操作将当前节点状态从CONDITION改为0
//修改不成功说明已经被取消了
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
/*
* Splice onto queue and try to set waitStatus of predecessor to
* indicate that thread is (probably) waiting. If cancelled or
* attempt to set waitStatus fails, wake up to resync (in which
* case the waitStatus can be transiently and harmlessly wrong).
*/
//调用AQS的enq方法将当前节点添加到同步队列中去
Node p = enq(node);
int ws = p.waitStatus;
//如果节点的状态为CANCEL或者修改状态为SIGNAL失败则直接唤醒
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
复制代码
通知的流程:
- 判断当前线程是否已经获得锁了
- 将头节点取出来
- 将等待队列中的头节点修改为原来的头节点的后继节点
- 修改当前节点的状态为从CONDITION改为0初始状态,修改不成功就返回false
- 将当前节点加入到同步队列中去
- 判断当前节点的状态是否为CANCEL或者修改状态为SIGNAL失败,则直接唤醒
被唤醒后的线程,将从await()方法中的while循环中退出(isOnSyncQueue()返回true),进而调用同步器的acquireQueued方法不断自旋直到获得锁。
signalAll方法相当于对每个方法都执行了一遍signal方法。