wait notify notifyAll await signal signalAll 的理解及示例
从常见的一道面试题开始,题目的描述是这样子的:
有三个线程分别打印A、B、C,请用多线程编程实现,在屏幕上循环打印10次ABCABC…
网上大都教了你怎么去实现,其实我也写过一篇 https://blog.csdn.net/sanri1993/article/details/89644493 但是都没有把原理说透,说再多的解法别人也记不住。
这个其实需要从最原本的 Object 的方法 wait() ,notify() notifyAll() 来理解 ,想必读者在工作中应该几乎是没有使用过这几个方法的,这里我稍微介绍下功能
- 这几个方法都必须在线程获得锁之后才能调用(这里说的锁是
synchronized锁) - wait 方法会阻塞当前线程并释放锁,直到被唤醒。即另一个线程获得那把锁,并调用锁对象的 notify 或 notifyAll 方法
- notify 调用后,wait 方法并不是立马往后执行,它需要重新获取锁
- wait 调用后会把当前线程放进一个 wait 集合中,那个集合并不是有序的
利用 wait 的阻塞特性,我们可以用它来实现循环打印 ABC ;可以使用三把锁来实现,还需要一个变量来控制当前应该打印谁,当前如果不是打印这个值时,调用 wait,如果是打印这个值,就打印这个值并切换成一个打印变量,同时唤醒下一个打印,伪代码如下:
完整代码 ABCThreadWait 在 这个地方
char currentChar = 'A';
Object lockA = new Object();
Object lockB = new Object();
Object lockC = new Object();
ThreadA
synchronized(lockA){
// 先获取 A 锁,可能马上就要调用 wait 方法
// 因为这里只有三个线程,所以用 if 和 while 是一样的,建议用 while
if(currentChar != 'A'){
lockA.wait();
}
print('A');currentChar = 'B';
//然后唤醒 B ,需要先获取B 锁
synchronized(lockB){
lockB.notify();
}
}
ThreadB
synchronized(lockB){
if(currentChar != 'B'){
lockB.wait();
}
print('B');currentChar = 'C';
synchronized(lockC){
lockC.notify();
}
}
ThreadC
synchronized(lockC){
if(currentChar != 'C'){
lockC.wait();
}
print('C');currentChar = 'A';
synchronized(lockA){
lockA.notify();
}
}
这里可以精简为使用一个线程类,使用不同的线程实例来实现,但还是使用的三把锁,每个线程都需要先获取自身的锁,然后判断是否需要打印,如果不是当前字符则释放锁;是当前字符就打印字符,在打印完后获取下一个打印的锁,把下一个打印线程唤醒。伪代码如下:
完整代码 ABCThreadWaitOneThreadClass 在 这个地方
synchronized (lockSelf){
if(printChar != currentPrintChar){
try {
lockSelf.wait();
} catch (InterruptedException e) {e.printStackTrace();}
}
// 打印当前线程字符
System.out.print(printChar);
// 切换下一个线程,并切换状态
if(currentPrintChar == 'A'){currentPrintChar = 'B';}
else if(currentPrintChar == 'B'){currentPrintChar = 'C';}
else if(currentPrintChar == 'C'){currentPrintChar = 'A';}
//唤醒下一个线程
synchronized (lockNext) {
lockNext.notify();
}
}
当然也可以使用一把锁来实现,这需要用到 Lock + Condition ,关于 Lock 和 Condition 见这篇文章
使用 Condition 的 await signal signalAll 时,同样需要获得 Lock 锁,其它特性等同于 wait notify notifyAll
其实仔细看这道题,永远只有一个线程在打印,照理论来说只需要一把锁即可,上面需要有多把锁的原因是一个锁只有一个等待队列 ,并且 notify 也是随机唤醒的。而每个 Condition 会带一个等待队列,所以用 Condition 只要一把锁就可以了,减轻了代码的复杂度,多锁情况很容易造成死锁。
使用 Lock + Condition 的完整代码 ConditionABC 在 这个地方 ,这是用多个线程类实现的,当然也可以用单个线程类多个线程实例来实现,这里就不再写了。
借用一篇写得挺不错的博文 ,请一定耐心把它读完再接着往下读 你真的懂 wait notify notifyAll 吗
文章中的源码QueueUseWaitNotify 在 这个地方
这个图不错,收藏了

文章中有一个地方说得挺好,就是面试常问的 notify 和 notifyAll 的区别
青铜玩家会一脸纯真的看着面试官,就是唤醒一个和唤醒一堆啊,但它两真正的区别是 notifyAll 调用后,会把所有在 Wait Set 中的线程状态变成 RUNNABLE 状态,然后这些线程再去竞争锁,获取到锁的线程为 Run 状态,没有获取到锁的线程进入 Entry Set 集合中变成 Block 状态,它们只需要等到上个线程执行完或者 wait 就可以再次竞争锁而无需 notify ; 而 notify 方法只是照规则唤醒 Wait Set 中的某一个线程,其它的线程还是在 Wait Set 中。
文章中说到的为什么 wait 要写在 for 循环中是因为 wait 是释放了锁,然后阻塞,等到下次唤醒的时候,在多个生产者多个消费者的情况下,有可能是被 “同类” 唤醒的,所以需要再去检查下状态是否正确。
文章中有一个地方没有说明白 ,这里再解释下,就是那个使用 notfiy 会带来死锁的问题,个人理解,如有偏差望指正
当有多个消费者和多个生产者的时候,这时正好在消费,所以生产者是在 Wait Set 中,可能还有其它消费者也在 Wait Set 中,因为是 notify 而不是 notfiyAll 嘛,所以消费者有可能一直 notify 的都是另一个消费者,刚好这时 buffer 空了,正好所有消费都 wait 了而没能及时 notify 生产者,这时 Wait Set 中四目相望造成死锁。
文章最后有一个评论说可以生产者用一把锁,消费者用一把锁,这里也有实现 QueueUseWaitNofiy2
可以使用 Condition 做更好的实现,只使用一把锁,这里本身也只需要一把锁就可以了,具体实现见代码 QueueUseCondition
一点小推广
创作不易,希望可以支持下我的开源软件,及我的小工具,欢迎来 gitee 点星,fork ,提 bug 。
Excel 通用导入导出,支持 Excel 公式
博客地址:https://blog.csdn.net/sanri1993/article/details/100601578
gitee:https://gitee.com/sanri/sanri-excel-poi
使用模板代码 ,从数据库生成代码 ,及一些项目中经常可以用到的小工具
博客地址:https://blog.csdn.net/sanri1993/article/details/98664034
gitee:https://gitee.com/sanri/sanri-tools-maven
wait notify notifyAll await signal signalAll 的理解及示例的更多相关文章
- Java并发学习 & Executor学习 & 异常逃逸 & 同步互斥Best Practice & wait/notify, conditon#await/signal
看了这篇文章:http://www.ciaoshen.com/2016/10/28/tij4-21/ 有一些Java并发的内容,另外查了一些资料. 朴素的Thread 首先,Java中关于线程Thre ...
- JUC在深入面试题——三种方式实现线程等待和唤醒(wait/notify,await/signal,LockSupport的park/unpark)
一.前言 在多线程的场景下,我们会经常使用加锁,来保证线程安全.如果锁用的不好,就会陷入死锁,我们以前可以使用Object的wait/notify来解决死锁问题.也可以使用Condition的awai ...
- 使用ReentrantLock和Condition来代替内置锁和wait(),notify(),notifyAll()
使用ReentrantLock可以替代内置锁,当使用内置锁的时候,我们可以使用wait() nitify()和notifyAll()来控制线程之间的协作,那么,当我们使用ReentrantLock的时 ...
- java 多线程 22 :生产者/消费者模式 进阶 利用await()/signal()实现
java多线程15 :wait()和notify() 的生产者/消费者模式 在这一章已经实现了 wait/notify 生产消费模型 利用await()/signal()实现生产者和消费者模型 一样 ...
- java 并发——理解 wait / notify / notifyAll
一.前言 前情简介: java 并发--内置锁 java 并发--线程 java 面试是否有被问到过,sleep 和 wait 方法的区别,关于这个问题其实不用多说,大多数人都能回答出最主要的两点区别 ...
- “全栈2019”Java多线程第三十三章:await与signal/signalAll
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
- Java Object对象中的wait,notify,notifyAll的理解
wait,notify,notifyAll 是定义在Object类的实例方法,用于控制线程状态,在线程协作时,大家都会用到notify()或者notifyAll()方法,其中wait与notify是j ...
- notify notifyAll 死锁
从一个死锁分析wait,notify,notifyAll 泡芙掠夺者 关注 2017.08.24 22:00* 字数 1361 阅读 249评论 3喜欢 7赞赏 1 本文通过wait(),notify ...
- 使用Object的wait,notify,notifyAll做线程调度
我们知道java中的所有类的祖先都是Object,Object类有四个个方法wait(),wait(long timeout),notify(),notifyAll(),这四个方法可以用来做线程的调度 ...
随机推荐
- 易初大数据 2019年10月24日 spss笔记 王庆超
数据文件的重置结构:横向结构(个案组),纵向结构,不符合分析方法的时候就需要重组,选定变量重组为个案,数据—重构,重构数据向导,选定变量重组为个案,将选定个案重构位变量,转置所有数据,变量组数目,一个 ...
- python的基础认识
一.python的简介 python的创始人为吉多·范罗苏姆(Guido van Rossum).1989年的圣诞节期间,Guido开始写能够解释Python语言语法的解释器.Python这个名 ...
- 快速搭建Jenkins集群
关于Jenkins集群 在Jenkins上同时执行多个任务时,单机性能可能达到瓶颈,使用Jenkins集群可以有效的解决此问题,让多台机器同时处理这些任务可以将压力分散,对单机版Jenkins的单点故 ...
- springboot~高并发下耗时操作的实现
高并发下的耗时操作 高并发下,就是请求在一个时间点比较多时,很多写的请求打过来时,你的服务器承受很大的压力,当你的一个请求处理时间长时,这些请求将会把你的服务器线程耗尽,即你的主线程池里的线程将不会再 ...
- nyoj 412-Same binary weight (bitset ,to_ulong())
412-Same binary weight 内存限制:64MB 时间限制:0ms 特判: No 通过数:2 提交数:3 难度:3 题目描述: The binary weight of a posit ...
- 领扣(LeetCode)最大连续1的个数 个人题解
给定一个二进制数组, 计算其中最大连续1的个数. 示例 1: 输入: [1,1,0,1,1,1] 输出: 3 解释: 开头的两位和最后的三位都是连续1,所以最大连续1的个数是 3. 注意: 输入的数组 ...
- (线段树)A Corrupt Mayor's Performance Art
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5023 题意: 区间更新, 区间询问: 题解: 区间更新, 区间询问, 一共30种颜色, 可用int 来 ...
- apple平台下的objc的GCD,多线程编程就是优雅自然。
在apple的操作系统平台里,GCD使得多线程编程是那么的优雅自然.在传统的多线程编程中,首先要写线程处理循环:之后还有事件队列,消息队列:还要在线程循环中分离事件解释消息,分派处理:还要考虑线程间是 ...
- 数据库05 使用percona软件来进行数据备份
1.为什么要与用percona来备份 常见的MySQL备份工具 —跨平台性差 —备份时间长.冗余备份.浪费存储空间 mysqldump备份缺点: —效率较低.备份与还原速度慢,锁表(即备份数据库中的一 ...
- eNSP仿真软件之利用单臂路由实现VLAN间路由
1. 实验原理 以太网中,通常会使用VLAN技术隔离二层广播域来减少广播的影响,并增强网络的安全性和可管理性.其缺点是同时也严格地隔离了不同VLAN之间的任何二层流量,使分属于不同VLAN的用户不能直 ...