接上一篇AQS,所以编号从四开始
在前面学习 synchronized 的时候,有讲到 wait/notify 的基本使用,结合
synchronized 可以实现对线程的通信。那么这个时候我就在思考了,既然 J.U.C 里
面提供了锁的实现机制,那 J.U.C 里面有没有提供类似的线程通信的工具呢? 于
是找阿找,发现了一个 Condition 工具类。
Condition 是一个多线程协调通信的工具类,可以让某些线程一起等待某个条件
(condition),只有满足条件时,线程才会被唤醒
public class ConditionDemoWait implements Runnable{
private Lock lock;
private Condition condition;
public ConditionDemoWait(Lock lock, Condition condition){
this.lock=lock;
this.condition=condition;
}
@Override
public void run() {
System.out.println("begin -ConditionDemoWait");
try {
lock.lock();
condition.await();//阻塞
System.out.println("end - ConditionDemoWait");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
public class ConditionDemoSignal implements Runnable{
private Lock lock;
private Condition condition;
public ConditionDemoSignal(Lock lock, Condition condition){
this.lock=lock;
this.condition=condition;
}
@Override
public void run() {
System.out.println("begin -ConditionDemoSignal");
try {
lock.lock();
condition.signal();
System.out.println("end - ConditionDemoSignal");
}finally {
lock.unlock();
}
}
}
总结:通过这个案例简单实现了 wait 和 notify 的功能,当调用 await 方法后,当前线程
会释放锁并等待,而其他线程调用 condition 对象的 signal 或者 signalall 方法通
知并被阻塞的线程,然后自己执行 unlock 释放锁,被唤醒的线程获得之前的锁继
续执行,最后释放锁。所以,condition 中两个最重要的方法,一个是 await,一个是 signal 方法
await:把当前线程阻塞挂起signal:唤醒阻塞的线程
调用 Condition,需要获得 Lock 锁,所以意味着会存在一个 AQS 同步队列,先来看 Condition.await 方法
**1. condition.await **
调用 Condition 的 await()方法(或者以 await 开头的方法),会使当前线程进入等
待队列并释放锁,同时线程状态变为等待状态。当从 await()方法返回时,当前线程一定获取了 Condition 相关联的锁
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter(); //创建一个新的节点,节点状态为 condition,采用的数据结构仍
然是链表
int savedState = fullyRelease(node); //释放当前的锁,得到锁的状态,并唤醒 AQS 队列中的一个线程
int interruptMode = 0;
//如果当前节点没有在同步队列上,即还没有被 signal,则将当前线程阻塞
while (!isOnSyncQueue(node)) {//判断这个节点是否在 AQS 队列上,第一次判断的是 false,因为前面已
经释放锁了
LockSupport.park(this); // 第一次总是 park 自己,开始阻塞等待
// 线程判断自己在等待过程中是否被中断了,如果没有中断,则再次循环,会在 isOnSyncQueue 中判断自己是否在队列上.
// isOnSyncQueue 判断当前 node 状态,如果是 CONDITION 状态,或者不在队列上了,就继续阻塞.
// isOnSyncQueue 判断当前 node 还在队列上且不是 CONDITION 状态了,就结束循环和阻塞.
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 当这个线程醒来,会尝试拿锁, 当 acquireQueued 返回 false 就是拿到锁了.
// interruptMode != THROW_IE -> 表示这个线程没有成功将 node 入队,但 signal 执行了 enq 方法让
其入队了.
// 将这个变量设置成 REINTERRUPT.
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
// 如果 node 的下一个等待者不是 null, 则进行清理,清理 Condition 队列上的节点.
// 如果是 null ,就没有什么好清理的了.
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
// 如果线程被中断了,需要抛出异常.或者什么都不做
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
**2. Condition.signal **
调用 Condition 的 signal()方法,将会唤醒在等待队列中等待时间最长的节点(首节点),在唤醒节点之前,会将节点移到同步队列中
public final void signal() {
if (!isHeldExclusively()) //先判断当前线程是否获得了锁
throw new IllegalMonitorStateException();
Node first = firstWaiter; // 拿到 Condition 队列上第一个节点
if (first != null)
doSignal(first);
}
3. Condition.doSignal
private void doSignal(Node first) {
do {
// 如果第一个节点的下一个节点是 null, 那么,最后一个节点也是 null.
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null; // 将 next 节点设置成 null
first.nextWaiter = null;
} while (!transferForSignal(first) &&(first = firstWaiter) != null);
}
该方法先是 CAS 修改了节点状态,如果成功,就将这个节点放到 AQS 队列中,
然后唤醒这个节点上的线程。此时,那个节点就会在 await 方法中苏醒
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
Node p = enq(node);
int ws = p.waitStatus;
// 如果上一个节点的状态被取消了, 或者尝试设置上一个节点的状态为 SIGNAL
失败了(SIGNAL 表示: 他的 next 节点需要停止阻塞),
if (ws > 0 || !compareAndSetWaitStatus(p, ws,
Node.SIGNAL))
LockSupport.unpark(node.thread); // 唤醒输入节点上的线程.
return true;
}
4. AQS.transferForSignal
该方法先是 CAS 修改了节点状态,如果成功,就将这个节点放到 AQS 队列中,
然后唤醒这个节点上的线程。此时,那个节点就会在 await 方法中苏醒
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
Node p = enq(node);
int ws = p.waitStatus;
// 如果上一个节点的状态被取消了, 或者尝试设置上一个节点的状态为 SIGNAL 失败了(SIGNAL 表示: 他的 next 节点需要停止阻塞),
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread); // 唤醒输入节点上的线程.
return true;
}
Condition 总结
阻塞:await()方法中,在线程释放锁资源之后,如果节点不在 AQS 等待队
列,则阻塞当前线程,如果在等待队列,则自旋等待尝试获取锁
释放:signal()后,节点会从 condition 队列移动到 AQS 等待队列,则进入
正常锁的获取流程