【面试:并发篇16:多线程:wait/notify详解】原理及错误用法(虚假唤醒等)
00.前言
如果有任何问题,请指出。
01.介绍
我们之前学习的过程中浅显的了解过wiat/notify,但是没有系统的介绍过wait/notify,wait是使线程陷入等待 notify是随机唤醒一个被wait的线程。
02.工作原理
当一个线程获取锁后 但是发现自己不满足某些条件 不能执行锁住部分的代码块时 需要进入等待列表 直到满足条件时才会重新竞争线程
上图为它的工作原理
注意
1.Owner发现条件某个线程不满足条件,调用wait方法,此时这个线程进入WaitSet,并且这个线程的状态变为WAITING状态
2.BLOCKED和WAITING状态的线程都不参与cpu调度,不占用cpu时间片
3.WAITING线程会在Owner线程调用notify或notifyAll时唤醒,但唤醒后仍然要进入EntryList重新竞争锁
03.API介绍
obj.wait():wait方法让进入object监视器的线程到waitSet等待。wait后会释放对象锁,让其他线程竞争
obj.wait(Long timeout):wait的有时限方法,如果在时限内没有其他线程唤醒,则自己直接唤醒自己,若期间有别的线程唤醒那就正常唤醒。wait后会释放对象锁,让其他线程竞争
obj.notify():notify方法让正在waitSet等待的线程挑一个唤醒
obj.notifyAll():notifyAll方法让正在waitSet等待的线程全部唤醒
它们都是线程之间进行协作的手段,都属于Object对象方法,必须获取此对象的锁,才能调用这几个方法,如果不加锁直接调用这些方法会报错
notify与notifyAll的对比
@Slf4j(topic = "c.TestWaitNotify")
public class TestWaitNotify {
final static Object obj = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (obj) {
log.debug("执行....");
try {
obj.wait(); // 让线程在obj上一直等待下去
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("其它代码....");
}
},"t1").start();
new Thread(() -> {
synchronized (obj) {
log.debug("执行....");
try {
obj.wait(); // 让线程在obj上一直等待下去
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("其它代码....");
}
},"t2").start();
// 主线程两秒后执行
sleep(0.5);
log.debug("唤醒 obj 上其它线程");
synchronized (obj) {
// obj.notify(); // 唤醒obj上一个线程
obj.notifyAll(); // 唤醒obj上所有等待线程
}
}
}
结果
调用notify时:
23:11:55.798 c.TestWaitNotify [t1] - 执行…
23:11:55.801 c.TestWaitNotify [t2] - 执行…
23:11:56.300 c.TestWaitNotify [main] - 唤醒 obj 上其它线程
23:11:56.300 c.TestWaitNotify [t1] - 其它代码…调用notifyAll时:
23:12:26.195 c.TestWaitNotify [t1] - 执行…
23:12:26.198 c.TestWaitNotify [t2] - 执行…
23:12:26.699 c.TestWaitNotify [main] - 唤醒 obj 上其它线程
23:12:26.699 c.TestWaitNotify [t2] - 其它代码…
23:12:26.699 c.TestWaitNotify [t1] - 其它代码…
解释
可以看出notify是随机唤醒一个线程,notifyAll则是唤醒全部线程
04.wait与sleep方法的区别
区别
1.sleep是Thread的类方法,而wait是Object的对象方法
2.sleep不需要强制和synchronized配合使用,但是wait需要和synchronized一起用
3.sleep在睡眠的同时,不会释放对象锁,但wait在等待时会释放对象锁
4.无时限wait方法执行后 线程变为WAITING状态,有时限的wait方法与sleep方法执行后变为TIMED_WAITING状态
分析下面代码
@Slf4j(topic = "c.Test19")
public class Test19 {
static final Object lock = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (lock) {
log.debug("获得锁");
try {
// Thread.sleep(2000);
lock.wait(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1").start();
Sleeper.sleep(1);
synchronized (lock) {
log.debug("获得锁");
}
}
}
结果
当调用sleep时的情况:
23:20:48.788 c.Test19 [t1] - 获得锁
当调用wait时的情况:
23:21:27.759 c.Test19 [t1] - 获得锁
23:21:28.768 c.Test19 [main] - 获得锁
解释
上述结果说明sleep在暂停期间 不会释放锁 导致 这期间其他线程不能运行,而wait则可以释放锁
05.wait/notify的正确使用情况
既然是正确的使用情况,那就需要一步一步来,把不正确的部分逐渐优化。
例子说明
现在有一群人需要干活,其中一个人叫做小南 他必须吸烟时才能干活。现在就是针对这个问题进行模拟。
模拟一
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.TestCorrectPosture")
public class TestCorrectPostureStep1 {
static final Object room = new Object(