李林超博客
首页
归档
留言
友链
动态
关于
归档
留言
友链
动态
关于
首页
Java
正文
17.并发编程之wait notify
Leefs
2022-10-19 PM
482℃
0条
[TOC] ### 一、原理 ![17.并发编程之wait notify01.png](https://lilinchao.com/usr/uploads/2022/10/2682559411.png) **分析** + Owner 线程发现条件不满足,调用 wait 方法,即可进入 WaitSet 变为 WAITING 状态 + BLOCKED 和 WAITING 的线程都处于阻塞状态,不占用 CPU 时间片 + BLOCKED 线程会在 Owner 线程释放锁时唤醒 + WAITING 线程会在 Owner 线程调用 notify 或 notifyAll 时唤醒,但唤醒后并不意味者立刻获得锁,仍需进入EntryList 重新竞争 ### 二、API介绍 | **方法** | **说明** | | -------------------------- | ------------------------------------------------------------ | | **obj.wait()** | wait方法让进入object监视器的线程到waitSet等待。wait后会释放对象锁,让其他线程竞争。 | | **obj.wait(Long timeout)** | 限时等待。导致当前的线程等待,等待被其他线程唤醒,或者指定的时间timeout用完,线程不再等待。 | | **obj.notify()** | 在 object 上正在`waitSet` 等待的线程中挑一个唤醒。 | | **obj.notifyAll()** | 让 object 上正在 `waitSet` 等待的线程全部唤醒。 | 它们都是线程之间进行协作的手段,都属于 Object 对象的方法。必须获得此对象的锁,才能调用这几个方法。 **示例代码** ```java @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(2); log.debug("唤醒 obj 上其它线程"); synchronized (obj) { obj.notify(); // 唤醒obj上一个线程 // obj.notifyAll(); // 唤醒obj上所有等待线程 } } } ``` + **notify运行结果** ``` 22:36:43.159 c.TestWaitNotify [t1] - 执行... 22:36:43.161 c.TestWaitNotify [t2] - 执行... 22:36:45.171 c.TestWaitNotify [main] - 唤醒 obj 上其它线程 22:36:45.171 c.TestWaitNotify [t1] - 其它代码... ``` + **notifyAll运行结果** ``` 22:39:49.383 c.TestWaitNotify [t1] - 执行... 22:39:49.385 c.TestWaitNotify [t2] - 执行... 22:39:51.389 c.TestWaitNotify [main] - 唤醒 obj 上其它线程 22:39:51.389 c.TestWaitNotify [t2] - 其它代码... 22:39:51.389 c.TestWaitNotify [t1] - 其它代码... ``` `wait()` 方法会释放对象的锁,进入 WaitSet 等待区,从而让其他线程就机会获取对象的锁。无限制等待,直到 notify 为止。 `wait(long n)` 有时限的等待, 到 n 毫秒后结束等待,或是被 notify。 ### 三、wait notify的正确姿势 #### 3.1 sleep(long n) 和 wait(long n) 的区别 - sleep 是 Thread 的方法,而 wait 是 Object 的方法 - sleep 不需要强制和 synchronized 配合使用,但 wait 需要和 synchronized 一起用 - sleep 在睡眠的同时,不会释放对象锁,但 wait 在等待的时候会释放对象锁 - 无时限wait方法执行后线程变为WAITING状态,有时限的wait方法与sleep方法执行后变为TIMED_WAITING状态 #### 3.2 演变步骤 ##### step 1 ```java @Slf4j(topic = "c.TestCorrectPostureStep1") 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("没烟,先歇会!"); sleep(2); } log.debug("有烟没?[{}]",hasCigarette); if (hasCigarette){ log.debug("可以开始干活了"); } } },"Leefs").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("烟到了!"); } },"送烟的").start(); } } ``` **运行结果** ``` 22:50:16.626 c.TestCorrectPostureStep1 [Leefs] - 有烟没?[false] 22:50:16.628 c.TestCorrectPostureStep1 [Leefs] - 没烟,先歇会! ---------- 中间间隔2s ------- 22:50:18.638 c.TestCorrectPostureStep1 [Leefs] - 有烟没?[false] 22:50:18.638 c.TestCorrectPostureStep1 [送烟的] - 烟到了! 22:50:18.638 c.TestCorrectPostureStep1 [其他人] - 可以开始干活了 22:50:18.638 c.TestCorrectPostureStep1 [其他人] - 可以开始干活了 22:50:18.638 c.TestCorrectPostureStep1 [其他人] - 可以开始干活了 22:50:18.639 c.TestCorrectPostureStep1 [其他人] - 可以开始干活了 22:50:18.639 c.TestCorrectPostureStep1 [其他人] - 可以开始干活了 ``` **分析** + 通过sleep方法来使线程处于等待状态; + 【Leefs线程】必须睡足2s后才能醒来,就算烟提前送到,也无法立刻醒来; + 如果送烟的线程也加了 synchronized (room) 后,就好比【Leefs】在里面反锁了门睡觉,烟根本没法送进门,main 没加synchronized 就好像 main 线程是翻窗户进来的; + 当【Leefs线程】获得锁在睡眠时,其它干活的线程,都要一直阻塞,效率太低; + 解决方法,使用 wait - notify 机制。 ##### step 2 ```java @Slf4j(topic = "c.TestCorrectPostureStep2") public class TestCorrectPostureStep2 { 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(2000); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("有烟没?[{}]", hasCigarette); if(hasCigarette){ log.debug("可以开始干活了"); } } },"Leefs").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(); } } ``` **运行结果** ``` 23:05:18.333 c.TestCorrectPostureStep2 [Leefs] - 有烟没?[false] 23:05:18.336 c.TestCorrectPostureStep2 [Leefs] - 没烟,先歇会! 23:05:18.336 c.TestCorrectPostureStep2 [其他人] - 可以开始干活了 23:05:18.336 c.TestCorrectPostureStep2 [其他人] - 可以开始干活了 23:05:18.336 c.TestCorrectPostureStep2 [其他人] - 可以开始干活了 23:05:18.336 c.TestCorrectPostureStep2 [其他人] - 可以开始干活了 23:05:18.336 c.TestCorrectPostureStep2 [其他人] - 可以开始干活了 23:05:19.342 c.TestCorrectPostureStep2 [送烟的] - 烟到了! 23:05:19.343 c.TestCorrectPostureStep2 [Leefs] - 有烟没?[true] 23:05:19.343 c.TestCorrectPostureStep2 [Leefs] - 可以开始干活了 ``` + 解决了当【Leefs线程】处于睡眠状态时,其他线程可以获取锁不需等待释放锁的问题 + 但如果有其它线程也在等待条件呢?送烟的主线程notify会不会错误地叫醒其他线程呢? ##### step 3 ```java @Slf4j(topic = "c.TestCorrectPostureStep3") 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("没干成活..."); } } },"Leefs").start(); sleep(1); 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("没干成活..."); } } },"Jeyoo").start(); sleep(1); new Thread(() -> { synchronized (room) { hasTakeout = true; log.debug("外卖送到了!"); room.notify(); } },"送外卖的").start(); } } ``` **运行结果** ``` 23:12:26.760 c.TestCorrectPostureStep3 [Leefs] - 有烟没?[false] 23:12:26.762 c.TestCorrectPostureStep3 [Leefs] - 没烟,先歇会! 23:12:27.766 c.TestCorrectPostureStep3 [Jeyoo] - 外卖送到没?[false] 23:12:27.766 c.TestCorrectPostureStep3 [Jeyoo] - 没外卖,先歇会! 23:12:28.771 c.TestCorrectPostureStep3 [送外卖的] - 外卖送到了! 23:12:28.772 c.TestCorrectPostureStep3 [Leefs] - 有烟没?[false] 23:12:28.773 c.TestCorrectPostureStep3 [Leefs] - 没干成活... ``` - notify 只能随机唤醒一个 WaitSet 中的线程,这时如果有其它线程也在等待,那么就可能唤醒不了正确的线程,称之为【虚假唤醒】 - 解决方法,改为 notifyAll ##### step 4 ```java @Slf4j(topic = "c.TestCorrectPostureStep4") 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("没干成活..."); } } },"Leefs").start(); sleep(1); 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("没干成活..."); } } },"Jeyoo").start(); sleep(1); new Thread(() -> { synchronized (room) { hasTakeout = true; log.debug("外卖送到了!"); room.notifyAll(); } },"送外卖的").start(); } } ``` **运行结果** ``` 10:42:37.796 c.TestCorrectPostureStep5 [Leefs] - 有烟没?[false] 10:42:37.798 c.TestCorrectPostureStep5 [Leefs] - 没烟,先歇会! 10:42:38.801 c.TestCorrectPostureStep5 [Jeyoo] - 外卖送到没?[false] 10:42:38.801 c.TestCorrectPostureStep5 [Jeyoo] - 没外卖,先歇会! 10:42:39.809 c.TestCorrectPostureStep5 [送外卖的] - 外卖送到了! 10:42:39.810 c.TestCorrectPostureStep5 [Jeyoo] - 外卖送到没?[true] 10:42:39.810 c.TestCorrectPostureStep5 [Jeyoo] - 可以开始干活了 10:42:39.810 c.TestCorrectPostureStep5 [Leefs] - 没烟,先歇会! ``` + 用 notifyAll 仅解决某个线程的唤醒问题,但使用 if + wait 判断仅有一次机会,一旦条件不成立,就没有重新判断的机会了 + 解决方法,用 while + wait,当条件不成立,再次 wait ##### step 5 将 if 改为 while ```java if (!hasCigarette) { log.debug("没烟,先歇会!"); try { room.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } ``` 改动后 + wait方法被唤醒后才会执行后面代码,因此此处不会导致while循环空转 ```java while (!hasCigarette) { log.debug("没烟,先歇会!"); try { room.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } ``` **运行结果** ``` 23:20:07.016 c.TestCorrectPostureStep5 [Leefs] - 有烟没?[false] 23:20:07.019 c.TestCorrectPostureStep5 [Leefs] - 没烟,先歇会! 23:20:08.030 c.TestCorrectPostureStep5 [Jeyoo] - 外卖送到没?[false] 23:20:08.031 c.TestCorrectPostureStep5 [Jeyoo] - 没外卖,先歇会! 23:20:09.033 c.TestCorrectPostureStep5 [送外卖的] - 外卖送到了! 23:20:09.034 c.TestCorrectPostureStep5 [Jeyoo] - 外卖送到没?[true] 23:20:09.034 c.TestCorrectPostureStep5 [Jeyoo] - 可以开始干活了 23:20:09.034 c.TestCorrectPostureStep5 [Leefs] - 没烟,先歇会! ``` ### 总结:正确姿势 ```java synchronized(lock) { while(条件不成立) { lock.wait(); } // 干活 } //另一个线程 synchronized(lock) { lock.notifyAll(); } ``` *附参考原文地址* *《黑马程序员之并发编程》*
标签:
并发编程
非特殊说明,本博所有文章均为博主原创。
如若转载,请注明出处:
https://lilinchao.com/archives/2503.html
上一篇
16.并发编程之synchronized原理进阶(二)
下一篇
18.并发编程之保护性暂停模式
取消回复
评论啦~
提交评论
栏目分类
随笔
2
Java
326
大数据
229
工具
31
其它
25
GO
43
标签云
Kibana
VUE
持有对象
Stream流
算法
FileBeat
Beego
Filter
SpringBoot
机器学习
Linux
前端
JVM
国产数据库改造
Redis
Zookeeper
Elastisearch
Elasticsearch
队列
随笔
Golang基础
数据结构
Eclipse
高并发
JavaWeb
Java
序列化和反序列化
Python
Hbase
并发线程
友情链接
申请
范明明
庄严博客
Mx
陶小桃Blog
虫洞