Java并发(二十二)----wait notify的正确姿势
开始之前先看看,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();
}
Java并发(二十二)----wait notify的正确姿势的更多相关文章
- Java并发(十二):CAS Unsafe Atomic
一.Unsafe Java无法直接访问底层操作系统,而是通过本地(native)方法来访问.不过尽管如此,JVM还是开了一个后门,JDK中有一个类Unsafe,它提供了硬件级别的原子操作. 这个类尽管 ...
- JAVA基础知识总结:一到二十二全部总结
>一: 一.软件开发的常识 1.什么是软件? 一系列按照特定顺序组织起来的计算机数据或者指令 常见的软件: 系统软件:Windows\Mac OS \Linux 应用软件:QQ,一系列的播放器( ...
- Java设计模式(十二) 策略模式
原创文章,同步发自作者个人博客,http://www.jasongj.com/design_pattern/strategy/ 策略模式介绍 策略模式定义 策略模式(Strategy Pattern) ...
- 疯狂JAVA讲义---第十二章:Swing编程(五)进度条和滑动条
http://blog.csdn.net/terryzero/article/details/3797782 疯狂JAVA讲义---第十二章:Swing编程(五)进度条和滑动条 标签: swing编程 ...
- Java进阶(三十二) HttpClient使用详解
Java进阶(三十二) HttpClient使用详解 Http协议的重要性相信不用我多说了,HttpClient相比传统JDK自带的URLConnection,增加了易用性和灵活性(具体区别,日后我们 ...
- JAVA之旅(二十二)——Map概述,子类对象特点,共性方法,keySet,entrySet,Map小练习
JAVA之旅(二十二)--Map概述,子类对象特点,共性方法,keySet,entrySet,Map小练习 继续坚持下去吧,各位骚年们! 事实上,我们的数据结构,只剩下这个Map的知识点了,平时开发中 ...
- Java进阶(五十二)利用LOG4J生成服务日志
Java进阶(五十二)利用LOG4J生成服务日志 前言 由于论文写作需求,需要进行流程挖掘.前提是需要有真实的事件日志数据.真实的事件日志数据可以用来发现.监控和提升业务流程. 为了获得真实的事件日志 ...
- [Java并发编程(二)] 线程池 FixedThreadPool、CachedThreadPool、ForkJoinPool?为后台任务选择合适的 Java executors
[Java并发编程(二)] 线程池 FixedThreadPool.CachedThreadPool.ForkJoinPool?为后台任务选择合适的 Java executors ... 摘要 Jav ...
- “全栈2019”Java多线程第二十二章:饥饿线程(Starvation)详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
- “全栈2019”Java多线程第十二章:后台线程setDaemon()方法详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
随机推荐
- 手敲,Ascend算子开发入门笔记分享
本文分享自华为云社区<Ascend算子开发入门笔记>,作者: JeffDing . 基础概念 什么是Ascend C Ascend C是CANN针对算子开发场景推出的编程语言,原生支持C和 ...
- [ABC202E] Count Descendants 题解
Count Descendants 题目大意 给定一颗以 \(1\) 为根的树,多次询问求某点的子树中深度为给定值的点的个数. 思路分析 对于每个深度开一个 vector,从大到小存下这个深度的所有点 ...
- 研发日常踩坑-Mysql分页数据重复
踩坑描述: 写分页查询接口,order by和limit混用的时候,出现了排序的混乱情况 在进行第N页查询时,出现与第一前面页码的数据一样的记录. 问题 在MySQL中分页查询,我们经常会用limit ...
- C#基数排序算法
前言 基数排序是一种非比较性排序算法,它通过将待排序的数据拆分成多个数字位进行排序. 实现原理 首先找出待排序数组中的最大值,并确定排序的位数. 从最低位(个位)开始,按照个位数的大小进行桶排序,将元 ...
- APP攻防--反模拟器&反代理&反证书&真机逃逸&XP框架&Frida技术
APP攻防--反模拟器&反代理&反证书&真机逃逸&XP框架&Frida技术 APP抓包技术 关于APP抓包,使用burpsuite抓模拟器中的数据包,需要将模拟 ...
- 合并果子(lgP1090)
贪心. 每次取最小的两堆合并,最后即为正确答案.(我也不会证明/wq) 所以说主要问题就是怎么找最小的两堆. 由于中间不断有插入和删除,所以用优先队列. 扯不下去了 直接看代码吧. #include& ...
- 从这里开始,跟我一起搞懂 MySQL!
提前申明:<MySQL 基础实战>系列是学习极客时间林晓斌的<MySQL实战45讲>的整理和总结,希望大家仅做为学习使用! 架构示意图 Server 层:包括连接器.查询缓存. ...
- 快来让你的网页色彩绚丽--linear-gradient与radial-gradient
作者:WangMin 格言:努力做好自己喜欢的每一件事 随着前端技术的发展,单一的背景色已经满足不了客户的需求了,所以在前端开发中我们常常会用到一些渐变色的效果,这样可以使前端页面更加美观.那么渐变色 ...
- (Good topic)字符串的最大公因子 (3.21leetcode每日打卡)
对于字符串 S 和 T,只有在 S = T + ... + T(T 与自身连接 1 次或多次)时,我们才认定 "T 能除尽 S". 返回最长字符串 X,要求满足 X 能除尽 s ...
- Vue01-简介与入门
Vue 01. 简介 1.1 前端三大框架 目前前端最流行的三大框架: Vue React angular 1.2 Vue简介 Vue (读音 /vjuː/,类似于 view) ,也可以写成Vue.j ...