Java 线程总结(七) —— 显式条件
1、显式条件也可以被称做条件变量、条件队列、或条件。锁用于解决竞态条件问题,条件是线程间的协作机制。显式锁与 synchronzied 相对应,而显式条件与 wait/notify 相对应。wait/notify 与 synchronized 配合使用,显式条件与显式锁配合使用。条件与锁相关联,创建条件变量需要通过显式锁,Lock 接口定义了创建方法:Condition newCondition();
。
2、Condition 接口表示条件变量1
2
3
4
5
6
7
8
9
10
11
12public interface Condition {
void await() throws InterruptedException;
void awaitUninterruptibly();
// 等待时间也是相对时间,但参数单位是纳秒,返回值是 nanosTimeout 减去实际等待的时间,即剩余未用的等待时间
long awaitNanos(long nanosTimeout) throws InterruptedException;
// 等待时间是相对时间,如果由于等待超时返回,返回值为 false,否则为 true
boolean await(long time, TimeUnit unit) throws InterruptedException;
// 等待时间是绝对时间,如果由于等待超时返回,返回值为 false,否则为 true
boolean awaitUntil(Date deadline) throws InterruptedException;
void signal();
void signalAll();
}
除 awaitUninterruptibly() 之外,await 方法都是响应中断的,如果发生了中断,会抛出 InterruptedException,但中断标志位会被清空。awaitUninterruptibly() 方法不会由于中断结束,但当它返回时,如果等待过程中发生了中断,中断标志位会被设置。
3、一般而言,与 Object 的 wait 方法一样,调用 await 方法前需要先获取锁,如果没有锁,会抛出异常 IllegalMonitorStateException。await 在进入等待队列后,会释放锁,释放 CPU,当其他线程将它唤醒后,或等待超时后,或发生中断异常后,它都需要重新获取锁,获取锁后,才会从 await 方法中退出。 另外,与 Object 的 wait 方法一样,await 返回后,不代表其等待的条件就一定满足了,通常要将 await 的调用放到一个循环内,只有条件满足后才退出。
一般而言,signal/signalAll 与 notify/notifyAll 一样,调用它们需要先获取锁,如果没有锁,会抛出异常 IllegalMonitorStateException。signal 与 notify 一样,挑选一个线程进行唤醒,signalAll 与 notifyAll 一样,唤醒所有等待的线程,但这些线程被唤醒后都需要重新竞争锁,获取锁后才会从 await 调用中返回。
4、显式条件与显式锁配合,wait/notify 与 synchronized 配合。
5、显示条件的实现原理1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// ReentrantLock 类中的 newCondition 方法
public Condition newCondition() {
return sync.newCondition();
}
-------------------------
// ReentrantLock 内部类 Sync 中的 newCondition 方法
final ConditionObject newCondition() {
// ConditionObject 是 AQS 中定义的一个内部类
return new ConditionObject();
}
-------------------------
public class ConditionObject implements Condition, java.io.Serializable {
// 条件队列的头节点
private transient Node firstWaiter;
// 条件队列的尾节点
private transient Node lastWaiter;
}
ConditionObject 内部也有一个队列,表示条件等待队列。ConditionObject 是 AQS 的成员内部类,它可以直接访问 AQS 中的数据,比如 AQS 中定义的锁等待队列。
6、await 方法源码解析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
26public final void await() throws InterruptedException {
// 如果等待前中断标志位已被设置,直接抛异常
if (Thread.interrupted())
throw new InterruptedException();
// 1.为当前线程创建节点,加入条件等待队列
Node node = addConditionWaiter();
// 2.释放持有的锁
int savedState = fullyRelease(node);
int interruptMode = 0;
// 3.放弃CPU,进行等待,直到被中断或 isOnSyncQueue 变为 true
// isOnSyncQueue 为 true 表示节点被其他线程从条件等待队列
// 移到了外部的锁等待队列,等待的条件已满足
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 4.重新获取锁
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
// 5.处理中断,抛出异常或设置中断标志位
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
7、signal 方法源码解析1
2
3
4
5
6
7public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
doSignal 的代码就不列举了,其基本逻辑是:将节点从条件等待队列移到锁等待队列;调用 LockSupport.unpark 将线程唤醒。
8、总结:显示条件的本质就是一个队列,该队列提供了两个系列的方法,await 系列的方法用于条件不满足时将线程对象加入队列,signal 系列方法用于条件满足时从队列中释放线程对象。实际业务可以根据具体的条件是否满足来决定当前线程对象是加入队列等待,还是从队列中释放执行。