它们都是线程之间进行协作的手段,都属于 Object 对象的方法。必须获得此对象的锁,才能调用这几个方法
wait()
使当前线程等待,直到另一个线程为此对象调用notify()方法或notifyAll()方法。换句话说,这个方法的行为就像它只是执行wait(0)一样(如果没有当前对象notify()唤醒就会一直等待)。
synchronized (obj) {
// while 当线程被唤醒的再去判断是否满足条件
while (<condition does not hold条件不满足>)
obj.wait();
... // 当前行为的动作
}
}
代码举例
/**
* @ClassName WaitNotifyTest
* @author: shouanzh
* @Description WaitNotifyTest
* @date 2022/3/8 21:13
*/
@Slf4j
public class WaitNotifyTest {
static final Object obj = new Object();
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
synchronized (obj) {
log.debug("t1执行...");
try {
// 只有获得锁对象之后, 才能调用wait/notify
obj.wait(); // 此时t1线程进入WaitSet等待
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("t1其它代码...");
}
}, "t1").start();
new Thread(() -> {
synchronized (obj) {
log.debug("t2执行...");
try {
obj.wait(); // 此时t2线程进入WaitSet等待
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("t2其它代码...");
}
}, "t2").start();
// 让主线程等两秒在执行,为了`唤醒`,不睡的话,那两个线程还没进入waitSet,主线程就开始唤醒了
Thread.sleep(1000);
log.debug("唤醒waitSet中的线程!");
// 只有获得锁对象之后, 才能调用wait/notify
synchronized (obj) {
// obj.notify(); // 唤醒waitset中的一个线程
obj.notifyAll(); // 唤醒waitset中的全部等待线程
}
}
}
obj.notifyAll();
obj.notify();
Step 1
分析下面代码:
@Slf4j
public class WaitNotifyTest {
static final Object room = new Object();
static boolean hasCigarette = false; // 有没有烟
static boolean hasTakeout = false;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
synchronized (room) {
log.debug("有烟没?[{}]", hasCigarette);
if (!hasCigarette) {
log.debug("没烟,先歇会!");
try {
Thread.sleep(2000); // 会阻塞2s, 不会释放锁
} 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)?
//synchronized (room) { // 如果加锁的话, 送烟人也需要等待小南睡2s的时间,此时即使送到了,小南线程也将锁释放了..
hasCigarette = true;
log.debug("烟到了噢!");
//}
}, "送烟的").start();
}
}
2022-03-08 22:05:28 [小南] - 有烟没?[false]
2022-03-08 22:05:28 [小南] - 没烟,先歇会!
2022-03-08 22:05:29 [送烟的] - 烟到了噢!
2022-03-08 22:05:30 [小南] - 有烟没?[true]
2022-03-08 22:05:30 [小南] - 可以开始干活了
2022-03-08 22:05:30 [其它人] - 可以开始干活了
2022-03-08 22:05:30 [其它人] - 可以开始干活了
2022-03-08 22:05:30 [其它人] - 可以开始干活了
2022-03-08 22:05:30 [其它人] - 可以开始干活了
2022-03-08 22:05:30 [其它人] - 可以开始干活了
Process finished with exit code 0
@Slf4j
public class WaitNotifyTest {
static final Object room = new Object();
static boolean hasCigarette = false; // 有没有烟
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("可以开始干活了");
}
}
}, "小南").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();
}
}
2022-03-08 22:17:11 [小南] - 有烟没?[false]
2022-03-08 22:17:11 [小南] - 没烟,先歇会!
2022-03-08 22:17:11 [其它人] - 可以开始干活了
2022-03-08 22:17:11 [其它人] - 可以开始干活了
2022-03-08 22:17:11 [其它人] - 可以开始干活了
2022-03-08 22:17:11 [其它人] - 可以开始干活了
2022-03-08 22:17:11 [其它人] - 可以开始干活了
2022-03-08 22:17:12 [送烟的] - 烟到了噢!
2022-03-08 22:17:12 [小南] - 有烟没?[true]
2022-03-08 22:17:12 [小南] - 可以开始干活了
Process finished with exit code 0
解决了其它干活的线程阻塞的问题
但如果有其它线程也在等待条件呢?
Step3
@Slf4j
public class WaitNotifyTest {
static final Object room = new Object();
static boolean hasCigarette = false; // 有没有烟
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(); // 此时进入到waitset等待集合, 同时会释放锁
} 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();
}
}, "送外卖的").start();
}
}
notify 只能随机唤醒一个 WaitSet中的线程,这时如果有其它线程也在等待,那么就可能唤醒不了正确的线 程,称之为【虚假唤醒】
解决方法,改为 notifyAll
Step4: notifyAll
new Thread(() -> {
synchronized (room) {
hasTakeout = true;
log.debug("外卖到了噢!");
room.notifyAll();
}
}, "送外卖的").start();
2022-03-08 22:33:29 [小南] - 有烟没?[false]
2022-03-08 22:33:29 [小南] - 没烟,先歇会!
2022-03-08 22:33:29 [小女] - 外卖送到没?[false]
2022-03-08 22:33:29 [小女] - 没外卖,先歇会!
2022-03-08 22:33:30 [送外卖的] - 外卖到了噢!
2022-03-08 22:33:30 [小女] - 外卖送到没?[true]
2022-03-08 22:33:30 [小女] - 可以开始干活了
2022-03-08 22:33:30 [小南] - 有烟没?[false]
2022-03-08 22:33:30 [小南] - 没干成活...
Process finished with exit code 0
还是唤醒了小南, 小南还是回去看看送来的是外卖还是烟. 很麻烦, 怎么解决?
用 notifyAll仅解决某个线程的唤醒问题,但使用 if+ wait 判断仅有一次机会,一旦条件不成立,就没有重新 判断的机会了
解决方法,用while + wait,当条件不成立,再次 wait
Step5:使用while循环来解决虚假唤醒
@Slf4j
public class WaitNotifyTest {
static final Object room = new Object();
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(); // 此时进入到waitset等待集合, 同时会释放锁
} 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();
}
}
wait()操作的代码
synchronized (lock) {
while (条件不成立) {
lock.wait();
}
// 干活
}
// 另一个线程
synchronized (lock) {
// notifyAll() 唤醒
lock.notifyAll();
}