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(),这四个方法可以用来做线程的调度 ...
随机推荐
- 浅谈 KMP 算法
最近在复习数据结构,学到了 KMP 算法这一章,似乎又迷糊了,记得第一次学习这个算法时,老师在课堂上讲得唾沫横飞,十分有激情,而我们在下面听得一脸懵比,啥?这是个啥算法?啥玩意?再去看看书,完全听不懂 ...
- 关于swoole 定时器有时候无法清除的解决方法
关于swoole 定时器有时候无法清除的解决方法 有时候start里面写个定时器 有时候你关闭进程的时候 发现定时器还是可以进行 目前只有重启服务器才可以 清除 还有就是ps -ef | grep p ...
- vue踩坑 导出new Vue.Store首字母要大写
控制台报错 : Uncaught TypeError: vuex__WEBPACK_IMPORTED_MODULE_6__.default.store is not a constructor 根据 ...
- Unity中用Mesh画一个圆环
Probuider 前几天在做一个小项目的时候,用到了Unity自带的一个包ProBuilder其中的Arch生成1/4圆. 挺好玩的,可以在直接Unity中根据需要用Mesh定制生成图形,而不用建模 ...
- html与css连接代码
demo01.html: <!DOCTYPE html><html> <head> <meta charset="utf-8"> ...
- java多线程与线程并发四:线程范围内的共享数据
当多个线程操作同一个共有数据时,一个线程对共有数据的改变会影响到另一个线程.比如下面这个例子:两个线程调用同一个对象的的方法,一个线程的执行结果会影响另一个线程. package com.sky.th ...
- [java笔记] 最近学的一些笔记
1.@Override的用法 2.父类的返回值类型的范围,与子类返回值类型的返回的大小关系: 3.子类方法的权限修饰符,与子类方法的权限修饰符: 4.如果p1是一个对象,p2也是个对象,那么代码p1= ...
- nyoj 17-单调递增最长子序列 && poj 2533(动态规划,演算法)
17-单调递增最长子序列 内存限制:64MB 时间限制:3000ms Special Judge: No accepted:21 submit:49 题目描述: 求一个字符串的最长递增子序列的长度 如 ...
- Ubuntu 16.04 更改apt源
1 修改apt源配置文件,把/etc/apt/sources.list替换为以下内容: sudo gedit /etc/apt/sources.list deb http://mirrors.aliy ...
- Project Euler 62: Cubic permutations
立方数\(41063625 (345^3)\)的各位数重新排列形成另外两个立方数\(6623104 (384^3)\)和\(66430125 (405^3)\).事实上,\(41063625\)是满足 ...