李林超博客
首页
归档
留言
友链
动态
关于
归档
留言
友链
动态
关于
首页
Java
正文
17.并发编程之wait notify
Leefs
2022-10-19 PM
888℃
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
47
NLP
4
标签云
队列
算法
Java编程思想
Kafka
Python
Typora
Azkaban
Stream流
机器学习
Elasticsearch
MyBatis
NIO
哈希表
SQL练习题
Java阻塞队列
GET和POST
RSA加解密
Spark SQL
MyBatis-Plus
Elastisearch
Spark
DataWarehouse
设计模式
前端
nginx
Git
VUE
序列化和反序列化
FileBeat
Hbase
友情链接
申请
范明明
庄严博客
Mx
陶小桃Blog
虫洞
评论已关闭