场景:有一个变量
boolean i
,线程 A 负责修改i
的值,i
的值一旦被修改成true
,则线程 B 开启它的任务。线程A
、B
通过变量i
解耦。
对于这种场景,如何实现捏?
1.比较 low 的实现
线程 B 轮询变量i
的值
while(value != desire){
Thread.sleep(1000);
}
doSomething();
public class WaitNotifyThreadLow {
// 共享资源
private static boolean isReady;
public static void main(String[] args) throws InterruptedException {
new Wait().start();
TimeUnit.SECONDS.sleep(2);
new Notify().start();
}
/**
* 传菜员
*/
static class Wait extends Thread {
public void run() {
while (true) {
if (!isReady) {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
continue;
}
// 满足:dosomethint()
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
System.out.println("Wait Run at " + sdf.format(new Date()));
return;
}
}
}
/**
* 厨师
*/
static class Notify extends Thread {
public void run() {
// 做饭
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
isReady = true;
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
这种方式看似可以实现所需功能,但存在以下问题:
睡眠时间太长:难以确保及时性
在睡眠时,基本不消耗处理器资源,但如果睡得太久,就难以及时发现变化,也就是及时性难以确保睡眠时间太短,难以降低开销
如果降低睡眠时间,这样能及时感知条件变化,但可能消耗更多的处理器资源,造成了无端地浪费
Java 的通知/等待机制能够解决这个矛盾并实现所需功能。
2. Java 中的通知/等待机制
线程 A 调用了对象 O 的wait()
方法进入等待状态,另一个线程 B 调用了对象 O 的notify()
或notifyAll()
方法,线程 A 收到通知后从对象 O 的wait()
方法返回,进而执行后续操作。
上述两个线程通过对象 O 来完成交互,对象 O 上的wait()
、notify()/notifyAll()
的关系如同开关信号一样,用来完成等待方和通知方之间的交互工作。
一个线程修改了一个对象的值,另一个线程感知到了变化,然后进行相应操作。整个过程开始于一个线程,终止于另一个线程。
等待/通知的经典范式
- 等待方
a)获取对象的锁;b)如果条件不满足,调用对象的wait()
方法,被通知后仍要检查条件;c)条件满足则执行对应逻辑
synchronized(对象){
while(条件不满足){
对象.wait();
}
条件满足:对应的处理逻辑
}
- 通知方:
a)获取对象的锁;b)改变条件;c)通知所有等待在对象上的线程
synchronized(对象){
改变条件
对象.notify()/notifyAll();
}
应用场景:厨子、传菜员,传菜员的任务是上菜,厨子的任务是做菜
public class WaitNotifyTest {
// 条件
private static boolean flag;
// 用来解耦的对象
private static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread wait = new Wait();
wait.setName("Wait");
wait.start();
TimeUnit.SECONDS.sleep(2);
Thread notify = new Notify();
notify.setName("Notify");
notify.start();
}
static class Wait extends Thread {
@Override
public void run() {
super.run();
// 加锁
synchronized (lock) {
if (!flag) {
// 条件不满足
try {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
System.out.println("Wait flag is not desire. at " + sdf.format(new Date()));
// 等待
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 满足:doSomething
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
System.out.println("Wait Run at " + sdf.format(new Date()));
}
}
}
static class Notify extends Thread {
@Override
public void run() {
super.run();
// 加锁
synchronized (lock) {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
System.out.println("Notify Run at " + sdf.format(new Date()));
try {
// 前期的操作花费了2s
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 改变条件
flag = true;
// 通知所有等待在对象上的线程
lock.notify();
try {
// 后期的操作花费了2s
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
【注意】
使用
wait()
、notify()
、notifyAll()
之前需要先对调用对象加锁线程调用
wati()
后,状态由Running
变成Waiting
,并将当前线程放到对象的等待队列中notify()
、notifyAll()
调用后,等待线程依然不会从wait()
方法返回,而是需要调用notify()
、notifyAll()
的线程释放锁之后,等待线程才有机会从wait()
返回notify()
将等待队列中的一个等待线程从等待队列中移到同步队列中,notifyAll()
将等待队列中的所有等待线程从等待队列中移到同步队列中,被移动的线程状态由 Running 变成 Waiting从
wait()
方法返回的前提是获得了调用对象的锁wait()
调用后会立即释放锁,进入等待状态;notify()
之后不会立即释放锁,它是会等同步代码块执行完后才释放锁并通知其他线程