本文已参与「新人创作礼」活动,一起开启掘金创作之路。
wait/notify的正确使用情况
既然是正确的使用情况,那就需要一步一步来,把不正确的部分逐渐优化。
现在有一群人需要干活,其中一个人叫做小南 他必须吸烟时才能干活。现在就是针对这个问题进行模拟。
模拟一:
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.TestCorrectPosture")
public class TestCorrectPostureStep1 {
static final Object room = new Object();
static boolean hasCigarette = false; // 有没有烟
static boolean hasTakeout = false;
public static void main(String[] args) {
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();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
// 这里能不能加 synchronized (room)?
synchronized (room) { // 33行
hasCigarette = true;
log.debug("烟到了噢!");
}
}, "送烟的").start();
}
}
结果:
33行加了synchronized:
- 23:2516.146 c.TestCorrectPosture [小南] - 有烟没?[false]
- 23:25:16.149 c.TestCorrectPosture [小南] - 没烟,先歇会!
- 23:25:18.157 c.TestCorrectPosture [小南] - 有烟没?[false]
- 23:25:18.157 c.TestCorrectPosture [送烟的] - 烟到了噢!
- 23:25:18.157 c.TestCorrectPosture [其它人] - 可以开始干活了
- 23:25:18.157 c.TestCorrectPosture [其它人] - 可以开始干活了
- 23:25:18.158 c.TestCorrectPosture [其它人] - 可以开始干活了
- 23:25:18.158 c.TestCorrectPosture [其它人] - 可以开始干活了
- 23:25:18.158 c.TestCorrectPosture [其它人] - 可以开始干活了
33行不加synchronized:
- 23:26:19.464 c.TestCorrectPosture [小南] - 有烟没?[false]
- 23:26:19.468 c.TestCorrectPosture [小南] - 没烟,先歇会!
- 23:26:20.475 c.TestCorrectPosture [送烟的] - 烟到了噢!
- 23:26:21.470 c.TestCorrectPosture [小南] - 有烟没?[true]
- 23:26:21.470 c.TestCorrectPosture [小南] - 可以开始干活了
- 23:26:21.471 c.TestCorrectPosture [其它人] - 可以开始干活了
- 23:26:21.471 c.TestCorrectPosture [其它人] - 可以开始干活了
- 23:26:21.471 c.TestCorrectPosture [其它人] - 可以开始干活了
- 23:26:21.471 c.TestCorrectPosture [其它人] - 可以开始干活了
- 23:26:21.471 c.TestCorrectPosture [其它人] - 可以开始干活了
解释:
33行不加synchronized:
当我们不加synchronized时我们发现的问题是 由于小南调用了sleep 睡眠2s期间没有释放锁 所以此时其他线程加锁的代码块不能运行,导致其他人没有办法工作
33行加synchronized:
当加synchronize的时上述问题依旧没有解决,且出现一个新的问题,小南在sleep 2s期间 主线程的第33行因为加了synchronized导致hasCigarette并没有改变为true,所以此时小南在1s后没有收到烟 所以小南没有工作
模拟二
我们在之前的题目上再加一个 人物 小女,小女需要外卖才能工作
@Slf4j(topic = "c.TestCorrectPosture")
public class TestCorrectPostureStep3 {
static final Object room = new Object();
static boolean hasCigarette = false;
static boolean hasTakeout = false;
// 虚假唤醒
public static void main(String[] args) {
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) {
Thread thread = Thread.currentThread();
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();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
synchronized (room) {
hasTakeout = true;
log.debug("外卖到了噢!");
room.notify();
}
}, "送外卖的").start();
}
}
结果:
- 23:45:35.476 c.TestCorrectPosture [小南] - 有烟没?[false]
- 23:45:35.486 c.TestCorrectPosture [小南] - 没烟,先歇会!
- 23:45:35.486 c.TestCorrectPosture [小女] - 外卖送到没?[false]
- 23:45:35.486 c.TestCorrectPosture [小女] - 没外卖,先歇会!
- 23:45:36.483 c.TestCorrectPosture [送外卖的] - 外卖到了噢!
- 23:45:36.483 c.TestCorrectPosture [小南] - 有烟没?[false]
- 23:45:36.483 c.TestCorrectPosture [小南] - 没干成活…
解释:
我们发现在新增一个小女 之后 这个代码又出现了问题,刚开始 小南 小女条件都不满足不能工作,但是因为notify是唤醒某一个线程,导致 本来应该唤醒小女的线程 把小南唤醒了 但是没有给小南需要的条件,而且小女也因此没有机会活动外卖,导致小女与小女都没有干活
这种把不该唤醒的线程唤醒的情况叫做虚假唤醒
模拟三
@Slf4j(topic = "c.TestCorrectPosture")
public class TestCorrectPostureStep4 {
static final Object room = new Object();
static boolean hasCigarette = false;
static boolean hasTakeout = false;
public static void main(String[] args) {
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) {
Thread thread = Thread.currentThread();
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();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
synchronized (room) {
hasTakeout = true;
log.debug("外卖到了噢!");
room.notifyAll();
}
}, "送外卖的").start();
}
}
结果:
- 00:00:01.274 c.TestCorrectPosture [小南] - 有烟没?[false]
- 00:00:01.274 c.TestCorrectPosture [小南] - 没烟,先歇会!
- 00:00:01.274 c.TestCorrectPosture [小女] - 外卖送到没?[false]
- 00:00:01.274 c.TestCorrectPosture [小女] - 没外卖,先歇会!
- 00:00:02.284 c.TestCorrectPosture [送外卖的] - 外卖到了噢!
- 00:00:02.284 c.TestCorrectPosture [小女] - 外卖送到没?[true]
- 00:00:02.284 c.TestCorrectPosture [小女] - 可以开始干活了
- 00:00:02.284 c.TestCorrectPosture [小南] - 有烟没?[false]
- 00:00:02.284 c.TestCorrectPosture [小南] - 没干成活…
解释:
我们这次把notify换成了notifyAll,使得小女一定可以被唤醒 并且收到外卖。事实也是如此,小女获得了外卖 并且开始工作,但是小南依旧被唤醒了 并且没有收到烟 导致小南没有干活。
现在的情况是 虽然唤醒了应该唤醒的 小女线程,但是小南线程还是被错误唤醒了,依旧是虚假唤醒
建议使用的格式
synchronized(lock){
while(条件不成立){
lock.wait();
}
// 后续代码
}
// 另一个线程
synchronized(lock){
lock.notifyAll();
}
这样的写法避免了 虚假唤醒的情况 也保证了 唤醒的线程一定可以获得需要的条件 从而进行工作。