Java并发(二十二)----wait notify的正确姿势

发布时间:2024年01月11日

开始之前先看看,sleep(long n)wait(long n) 的区别:

1) sleep 是 Thread 的静态方法,而 wait 是 Object 的方法

2) sleep 不需要强制和 synchronized 配合使用,但 wait 需要和 synchronized 一起用

3) sleep 在睡眠的同时,不会释放对象锁的,但 wait 在等待的时候会释放对象锁

4) 它们状态 TIMED_WAITING

建议:锁对象加final修饰,这样锁对象不可变。

改进 1
static final Object room = new Object();
static boolean hasCigarette = false; ?// 是否有烟
static boolean hasTakeout = false;

思考下面的解决方案好不好,为什么?

new Thread(() -> {
 ? ?synchronized (room) {
 ? ? ? ?log.debug("有烟没?[{}]", hasCigarette);
 ? ? ? ?if (!hasCigarette) {
 ? ? ? ? ? ?log.debug("没烟,先歇会!");
 ? ? ? ? ? ?sleep(2);
 ? ? ?  }
 ? ? ? ?log.debug("有烟没?[{}]", hasCigarette);
 ? ? ? ?if (hasCigarette) {
 ? ? ? ? ? ?log.debug("可以开始干活了");
 ? ? ?  }
 ?  }
}, "小南").start();
?
for (int i = 0; i < 5; i++) {
 ? ?new Thread(() -> {
 ? ? ? ?synchronized (room) {
 ? ? ? ? ? ?log.debug("可以开始干活了");
 ? ? ?  }
 ?  }, "其它人").start();
}
?
sleep(1);
new Thread(() -> {
 ? ?// 这里能不能加 synchronized (room)?  不能,sleep不会释放对象锁 
 ? ?hasCigarette = true;
 ? ?log.debug("烟到了噢!");
}, "送烟的").start();

输出

20:49:49.883 [小南] c.TestCorrectPosture - 有烟没?[false]
20:49:49.887 [小南] c.TestCorrectPosture - 没烟,先歇会!
20:49:50.882 [送烟的] c.TestCorrectPosture - 烟到了噢!
20:49:51.887 [小南] c.TestCorrectPosture - 有烟没?[true]
20:49:51.887 [小南] c.TestCorrectPosture - 可以开始干活了
20:49:51.887 [其它人] c.TestCorrectPosture - 可以开始干活了
20:49:51.887 [其它人] c.TestCorrectPosture - 可以开始干活了
20:49:51.888 [其它人] c.TestCorrectPosture - 可以开始干活了
20:49:51.888 [其它人] c.TestCorrectPosture - 可以开始干活了
20:49:51.888 [其它人] c.TestCorrectPosture - 可以开始干活了
  • 其它干活的线程,都要一直阻塞,效率太低

  • 小南线程必须睡足 2s 后才能醒来,就算烟提前送到,也无法立刻醒来

  • 加了 synchronized (room) 后,就好比小南在里面反锁了门睡觉,烟根本没法送进门,main 没加 synchronized 就好像 main 线程是翻窗户进来的

  • 解决方法,使用 wait - notify 机制

改进 2

思考下面的实现行吗,为什么?

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();
}
?
sleep(1);
new Thread(() -> {
 ? ?synchronized (room) {
 ? ? ? ?hasCigarette = true;
 ? ? ? ?log.debug("烟到了噢!");
 ? ? ? ?room.notify();
 ?  }
}, "送烟的").start();

输出

20:51:42.489 [小南] c.TestCorrectPosture - 有烟没?[false]
20:51:42.493 [小南] c.TestCorrectPosture - 没烟,先歇会!
20:51:42.493 [其它人] c.TestCorrectPosture - 可以开始干活了
20:51:42.493 [其它人] c.TestCorrectPosture - 可以开始干活了
20:51:42.494 [其它人] c.TestCorrectPosture - 可以开始干活了
20:51:42.494 [其它人] c.TestCorrectPosture - 可以开始干活了
20:51:42.494 [其它人] c.TestCorrectPosture - 可以开始干活了
20:51:43.490 [送烟的] c.TestCorrectPosture - 烟到了噢!
20:51:43.490 [小南] c.TestCorrectPosture - 有烟没?[true]
20:51:43.490 [小南] c.TestCorrectPosture - 可以开始干活了
  • 解决了其它干活的线程阻塞的问题

  • 但如果有其它线程也在等待条件呢?

改进 3
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();
?
sleep(1);
new Thread(() -> {
 ? ?synchronized (room) {
 ? ? ? ?hasTakeout = true;
 ? ? ? ?log.debug("外卖到了噢!");
 ? ? ? ?room.notify();
 ?  }
}, "送外卖的").start();

输出

20:53:12.173 [小南] c.TestCorrectPosture - 有烟没?[false]
20:53:12.176 [小南] c.TestCorrectPosture - 没烟,先歇会!
20:53:12.176 [小女] c.TestCorrectPosture - 外卖送到没?[false]
20:53:12.176 [小女] c.TestCorrectPosture - 没外卖,先歇会!
20:53:13.174 [送外卖的] c.TestCorrectPosture - 外卖到了噢!
20:53:13.174 [小南] c.TestCorrectPosture - 有烟没?[false]
20:53:13.174 [小南] c.TestCorrectPosture - 没干成活...
  • notify 只能随机唤醒一个 WaitSet 中的线程,这时如果有其它线程也在等待,那么就可能唤醒不了正确的线程,称之为【虚假唤醒】

  • 解决方法,改为 notifyAll

改进 4
new Thread(() -> {
 ? ?synchronized (room) {
 ? ? ? ?hasTakeout = true;
 ? ? ? ?log.debug("外卖到了噢!");
 ? ? ? ?room.notifyAll();
 ?  }
}, "送外卖的").start();

输出

20:55:23.978 [小南] c.TestCorrectPosture - 有烟没?[false]
20:55:23.982 [小南] c.TestCorrectPosture - 没烟,先歇会!
20:55:23.982 [小女] c.TestCorrectPosture - 外卖送到没?[false]
20:55:23.982 [小女] c.TestCorrectPosture - 没外卖,先歇会!
20:55:24.979 [送外卖的] c.TestCorrectPosture - 外卖到了噢!
20:55:24.979 [小女] c.TestCorrectPosture - 外卖送到没?[true]
20:55:24.980 [小女] c.TestCorrectPosture - 可以开始干活了
20:55:24.980 [小南] c.TestCorrectPosture - 有烟没?[false]
20:55:24.980 [小南] c.TestCorrectPosture - 没干成活...
  • 用 notifyAll 仅解决某个线程的唤醒问题,但使用 if + wait 判断仅有一次机会,一旦条件不成立,就没有重新判断的机会了

  • 解决方法,用 while + wait,当条件不成立,再次 wait

改进 5

将 if 改为 while

if (!hasCigarette) {
 ? ?log.debug("没烟,先歇会!");
 ? ?try {
 ? ? ? ?room.wait();
 ?  } catch (InterruptedException e) {
 ? ? ? ?e.printStackTrace();
 ?  }
}

改动后

while (!hasCigarette) {
 ? ?log.debug("没烟,先歇会!");
 ? ?try {
 ? ? ? ?room.wait();
 ?  } catch (InterruptedException e) {
 ? ? ? ?e.printStackTrace();
 ?  }
}

输出

20:58:34.322 [小南] c.TestCorrectPosture - 有烟没?[false]
20:58:34.326 [小南] c.TestCorrectPosture - 没烟,先歇会!
20:58:34.326 [小女] c.TestCorrectPosture - 外卖送到没?[false]
20:58:34.326 [小女] c.TestCorrectPosture - 没外卖,先歇会!
20:58:35.323 [送外卖的] c.TestCorrectPosture - 外卖到了噢!
20:58:35.324 [小女] c.TestCorrectPosture - 外卖送到没?[true]
20:58:35.324 [小女] c.TestCorrectPosture - 可以开始干活了
20:58:35.324 [小南] c.TestCorrectPosture - 没烟,先歇会!

while循环 + wait 防止虚假唤醒,并且一般使用notifyAll来进行唤醒

synchronized(lock) {
 ? ?while(条件不成立) {
 ? ? ? ?lock.wait();
 ?  }
 ? ?// 干活
}
?
//另一个线程
synchronized(lock) {
 ? ?lock.notifyAll();
}
文章来源:https://blog.csdn.net/FaithWh/article/details/135539714
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。