ConditonObject 是 Condition 接口的实现类,用来做线程同步的通知等待,最重要的是它维护了一个单向的等待队列,要明确它和 AQS 队列的关系。此外还介绍了 LockSupport 类,Condition 的 await 底层就是通过 LockSupport 实现的。

等待通知 Condition

Condition 的等待队列是一个不带头节点的链式队列,而 AQS 的同步队列是带头节点的。

Condition 的等待队列

AQS 的同步队列

多个 Condition 对象的多个等待队列有什么用?

同步队列和等待队列的关系:

  1. 调用 Condition 的 await 方法,会使当前持有锁的线程进入同步队列,并释放锁,线程进入等待状态。相当于将同步队列的头节点(占用线程)移动到等待队列的尾部。

  2. 调用 Condition 的 signal 方法,唤醒该线程,相当于将等待队列的头节点移动到同步队列的尾部。

Condition 是个接口,它的实现类是 ConditionObject,是 AQS 的子类。

Condition 的 await 方法

当前线程调用 Condtion.await() 方法后,会加入等待队列然后释放 lock ,直到被唤醒或线程出现中断。

如何将当前线程加入等待队列

首先将等待队列中waitStatus!=-2(只有 waitStatus 为 Condition 的线程才能在等待队列当中)的节点从队列中删除,然后通过尾插法将当前线程封装为 Node 插入到等待队列当中。

当前线程释放锁的过程

调用 AQS 的 release 方法释放同步状态,并唤醒同步队列头节点的后继节点引用的线程。

怎样从 await 方法中退出

  1. 想要退出 await 方法,第一个前提条件就是要退出 while 循环:

退出 while 循环有两种可能:要么当前等待线程被中断,要么由于调用了 Condition.signal / signalAll 方法唤醒线程(当前线程从等待队列移动到同步队列)。

  • 退出 while 循环之后

await 原理

调用 Condition.await 方法的线程必须是已经获得 lock 的线程,也就是当前线程是同步队列中的头节点,调用该方法会使当前线程封装的 Node 尾插到等待队列中。

Condition 的 signal/signalAll 方法

调用 Condition 中的 signal 方法可以将等待队列中等待时间最长(由于等待队列是先进先出的,所以等待时间最长的是头节点)的节点移动到同步队列中,使得该节点有机会获得 lock。

signal 方法只会对等待队列的头节点进行操作,而 signalAll 方法则是通过一个 while 循环将等待队列所有节点移动到同步队列中。

Condition 的简单使用

Condition 就和 Object 一样,只是一个调用 await / wait 方法的一个对象(接口)。

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class ConditionSignal {
private static ReentrantLock lock = new ReentrantLock();
private static Condition condition = lock.newCondition();
private static boolean flag = false;

public static void main(String[] args) {
Thread waitThread = new Thread(new waiter());
Thread signalThread = new Thread(new signaller());
waitThread.start();
signalThread.start();
}

public static class waiter implements Runnable {
@Override
public void run() {
lock.lock();
try {
while (!flag) {
System.out.println(Thread.currentThread().getName() + "当前条件不满足等待");
try {
condition.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "条件满足,开始执行后续工作");
}
} finally {
lock.unlock();
}
}
}

public static class signaller implements Runnable {
@Override
public void run() {
lock.lock();
try {
flag = true;
condition.signalAll();
} finally {
lock.unlock();
}
}
}
}

线程阻塞唤醒类 LockSupport

synchronized 会使线程阻塞,线程会进入 BLOCKED 状态,而调用 LockSupprt 类的方法阻塞线程会使线程进入到 WAITING 状态。

park 阻塞的线程不仅仅会被 unpark 唤醒,还可能会被线程中断(Thread.interrupt)唤醒,而且不会抛出 InterruptedException 异常,所以建议在 park 后自行判断线程中断状态。

LockSupport 的优点