API 介绍
- obj.wait() 让进入 object 监视器的线程到 waitSet 等待
- obj.notify() 在 object 上正在 waitSet 等待的线程中挑一个唤醒
- obj.notifyAll() 让 object 上正在 waitSet 等待的线程全部唤醒
- wait() 方法会释放对象的锁,进入 WaitSet 等待区,从而让其他线程就机会获取对象的锁。无限制等待,直到被notify 为止
- wait(long n) 有时限的等待, 到 n 毫秒后结束等待,或是未到等待时间被 notify
它们都是线程之间进行协作的手段,都属于 Object 对象的方法。必须获得此对象的锁,才能调用这几个方法
另外:使用wait就是使用了重量级锁
wait notify 原理
- Owner 线程发现条件不满足,调用 wait 方法,即可进入 WaitSet 变为 WAITING 状态
- BLOCKED 和 WAITING 的线程都处于阻塞状态,不占用 CPU 时间片
- BLOCKED 线程会在 Owner 线程释放锁时唤醒
- WAITING 线程会在 Owner 线程调用 notify 或 notifyAll 时唤醒,但唤醒后并不意味者立刻获得锁,仍需进入EntryList 重新竞争
@Slf4j(topic = "c.test17:")
public class Test17 {
final static Object obj = new Object();
public static void main(String[] args) throws InterruptedException {
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();
// 主线程两秒后执行
Thread.sleep(2);
log.debug("唤醒 obj 上其它线程");
synchronized (obj) {
obj.notify(); // 唤醒obj上一个线程
//obj.notifyAll(); // 唤醒obj上所有等待线程
}
}
}
使用notify的其中一种运行结果
20:33:49.136 c.test2: [t1] - 执行....
20:33:49.143 c.test2: [main] - 唤醒 obj 上其它线程
20:33:49.144 c.test2: [t2] - 执行....
20:33:49.144 c.test2: [t1] - 其它代码....
使用notifyAll的其中一种运行结果
20:33:49.136 c.test2: [t1] - 执行....
20:33:49.143 c.test2: [main] - 唤醒 obj 上其它线程
20:33:49.144 c.test2: [t2] - 执行....
20:33:49.144 c.test2: [t1] - 其它代码....
20:33:49.144 c.test2: [t2] - 其它代码....
sleep(long n) 和 wait(long n) 的区别
API 介绍
- sleep(long n) 和 wait(long n) 的区别
- sleep 是 Thread 方法,而 wait 是 Object 的方法
- sleep 不需要强制和 synchronized 配合使用,但 wait 需要和 synchronized 一起用
- sleep 在睡眠的同时,不会释放对象锁的,但 wait 在等待的时候会释放对象锁
- 它们状态 TIMED_WAITING
下面的代码测试:sleep 在睡眠的同时,不会释放对象锁的,但 wait 在等待的时候会释放对象锁,并且在wait等待过程中,如果未到等待结束时间,其他线程唤醒该线程的时候,会结束wait
@Slf4j(topic = "c.test18:")
public class Test18 {
final static Object obj = new Object();
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
log.debug("t1开始执行....");
synchronized (obj) {
try {
//Thread.sleep(5000);
obj.wait(30000);
log.debug("t1执行中....");
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("t1执行完....");
}
},"t1").start();
// 主线程一秒后执行
Thread.sleep(1000);
synchronized (obj) {
log.debug("main执行");
obj.notify(); //测试wait(timeout)
}
}
}
关于sleep(long n) 和 wait(long n) 的实践
例一:
在这个例子中:小南线程不仅要拥有资源而且要有烟才能干活,在没有烟的情况下必须睡足 2s 后才能醒来,就算烟提前送到,也无法立刻醒来,而其他线程只需要拥有资源就能干活,但是这时因为小南拥有资源且不释放资源,其他线程只能等着。小南线程加了 synchronized (room) 后,就好比小南在里面反锁了门睡觉,烟根本没法送进门,main没加synchronized(room)就好像main线程是翻窗户进来给小南线程送烟的,才打破这种僵局
解决办法:使用 wait - notify 机制,下面例子2就是实践
public class Test19 {
static final Object room = new Object(); //相当于共享资源
static boolean hasCigarette = false;
public static void main(String[] args) throws InterruptedException {
//这个线程不仅要拥有资源而且必须有烟才能干活
new Thread(() -> {
synchronized (room) {
log.debug("有烟没?[{}]", hasCigarette);
if (!hasCigarette) {
log.debug("没烟,先歇会!");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("有烟没?[{}]", hasCigarette);
if (hasCigarette) {
log.debug("有烟可以开始干活了");
}
}
}, "小南").start();
//其他线程有共享资源就可以干活
for (int i = 0; i < 5; i++) {
new Thread(() -> {
synchronized (room) {
log.debug("可以开始干活了");
}
}, "其它人").start();
}
Thread.sleep(1000);
new Thread(() -> {
// 这里能不能加 synchronized (room)?
hasCigarette = true;
log.debug("烟到了噢!");
}, "送烟的").start();
}
}
例二:
使用wait-notify机制后,小南线程在没有烟的情况下使用wait等待,释放了锁,其他线程可以执行,在他等够2秒的时候发现烟到了,继续干活,这样就解决了了其它干活的线程阻塞的问题,但如果有其它线程也在等待条件呢?请看下面例三
@Slf4j(topic = "c.test20:")
public class Test20 {
static final Object room = new Object(); //相当于共享资源
static boolean hasCigarette = false;
public static void main(String[] args) throws InterruptedException {
//这个线程不仅要拥有资源而且必须有烟才能干活
new Thread(() -> {
synchronized (room) {
log.debug("有烟没?啊[{}]", hasCigarette);
if (!hasCigarette) {
log.debug("没烟,先歇会!");
try {
room.wait(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("有烟没?[{}]", hasCigarette);
if (hasCigarette) {
log.debug("有烟可以开始干活了");
}
}
}, "小南").start();
//其他线程有共享资源就可以干活
for (int i = 0; i < 5; i++) {
new Thread(() -> {
synchronized (room) {
log.debug("可以开始干活了");
}
}, "其它人").start();
}
Thread.sleep(1000);
new Thread(() -> {
synchronized (room) {
hasCigarette = true;
log.debug("烟到了噢!");
room.notify();
}
}, "送烟的").start();
}
}
例三:
在这个例子中:小南线程等待的是烟,而其他线程等待的是外卖,当主线程准备好其中一个资源(烟/外卖)如果使用notify唤醒了不是对应资源的线程,那么导致小南线程和其他线程还是执行不了,因为notify 只能随机唤醒一个 WaitSet 中的线程,这时如果有其它线程也在等待,那么就可能唤醒不了正确的线程,称之为【虚假唤醒】,因此要使用notifyAll
使用notifyAll仅解决某个线程的唤醒问题,但使用 if + wait 判断仅有一次机会,一旦条件不成立,就没有重新判断的机会了,因此要使用用 while + wait,当条件不成立,再次 wait,看下面例四:
@Slf4j(topic = "c.test21:")
public class Test21 {
static final Object room = new Object(); //相当于共享资源
static boolean hasCigarette = false;//hasCigarette和hasTakeout都是某一个线程执行所必须的结果
static boolean hasTakeout = false;
public static void main(String[] args) throws InterruptedException {
//这个线程不仅要拥有资源而且必须有烟才能干活
new Thread(() -> {
synchronized (room) {
log.debug("有烟没?啊[{}]", hasCigarette);
if (!hasCigarette) {
log.debug("没烟,先歇会!");
try {
room.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("有烟没?[{}]", hasCigarette);
if (hasCigarette) {
log.debug("有烟可以开始干活了");
}else {
log.debug("没干成活...");
}
}
}, "小南").start();
//其他线程有共享资源和外卖才可以干活
new Thread(() -> {
synchronized (room) {
log.debug("外卖送到没?[{}]", hasTakeout);
if (!hasTakeout) {
log.debug("没外卖,先歇会!");
try {
room.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("外卖送到没?[{}]", hasTakeout);
if (hasTakeout) {
log.debug("有外卖可以开始干活了");
} else {
log.debug("没干成活...");
}
}
}, "小女").start();
Thread.sleep(1000);
new Thread(() -> {
synchronized (room) {
hasTakeout = true;
log.debug("外卖到了噢!");
//room.notify();
room.notifyAll();
}
}, "送外卖").start();
}
}
例四:
@Slf4j(topic = "c.test21:")
public class Test22 {
static final Object room = new Object(); //相当于共享资源
//hasCigarette和hasTakeout都是某一个线程执行所必须的结果
static boolean hasCigarette = false;
static boolean hasTakeout = false;
public static void main(String[] args) throws InterruptedException {
//这个线程不仅要拥有资源而且必须有烟才能干活
new Thread(() -> {
synchronized (room) {
log.debug("有烟没?啊[{}]", hasCigarette);
while (!hasCigarette) {
log.debug("没烟,先歇会!");
try {
room.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("有烟没?[{}]", hasCigarette);
if (hasCigarette) {
log.debug("有烟可以开始干活了");
}else {
log.debug("没干成活...");
}
}
}, "小南").start();
//其他线程有共享资源就可以干活
new Thread(() -> {
synchronized (room) {
log.debug("外卖送到没?[{}]", hasTakeout);
if (!hasTakeout) {
log.debug("没外卖,先歇会!");
try {
room.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("外卖送到没?[{}]", hasTakeout);
if (hasTakeout) {
log.debug("有外卖可以开始干活了");
} else {
log.debug("没干成活...");
}
}
}, "小女").start();
Thread.sleep(1000);
new Thread(() -> {
synchronized (room) {
hasTakeout = true;
log.debug("外卖到了噢!");
room.notifyAll();
}
}, "送外卖").start();
new Thread(() -> {
synchronized (room) {
hasCigarette = true;
log.debug("烟到了!");
room.notifyAll();
}
}, "送烟").start();
}
}
通过上面四个例子我们总结出wait-notifyAll的使用模型
synchronized(lock) {
while(条件不成立) {
lock.wait();
}
// 干活
}
//另一个线程
synchronized(lock) {
lock.notifyAll();
}