Java synchronized 中的while 和 notifyAll

转载自  Java synchronized 中的while 和 notifyAll

 

问题1 为什么是while 而不是if

大多数人都知道常见的使用synchronized代码:

synchronized (obj) {while (check pass) {wait();}// do your business
}

那么问题是为啥这里是while而不是if呢? 
这个问题 我最开始也想了很久, 按理来说 已经在synchronized块里面了嘛 就不需要了. 这个也是我前面一直是这么认为的, 直到最近看了一个Stackoverflow上的问题, 才对这个问题有了比较深入的理解.

实现一个有界队列

试想我们要试想一个有界的队列. 那么常见的代码可以是这样:

    static class Buf {private final int MAX = 5;private final ArrayList<Integer> list = new ArrayList<>();synchronized void put(int v) throws InterruptedException {if (list.size() == MAX) {wait();}list.add(v);notifyAll();}synchronized int get() throws InterruptedException {// line 0 if (list.size() == 0) {  // line 1wait();  // line2// line 3}int v = list.remove(0);  // line 4notifyAll(); // line 5return v;}synchronized int size() {return list.size();}}

注意到这里用的if, 那么我们来看看它会报什么错呢? 
下面的代码用了1个线程来put ; 10个线程来get:

        final Buf buf = new Buf();ExecutorService es = Executors.newFixedThreadPool(11);for (int i = 0; i < 1; i++)es.execute(new Runnable() {@Overridepublic void run() {while (true ) {try {buf.put(1);Thread.sleep(20);}catch (InterruptedException e) {e.printStackTrace();break;}}}});for (int i = 0; i < 10; i++) {es.execute(new Runnable() {@Overridepublic void run() {while (true ) {try {buf.get();Thread.sleep(10);}catch (InterruptedException e) {e.printStackTrace();break;}}}});}es.shutdown();es.awaitTermination(1, TimeUnit.DAYS);

这段代码很快或者说一开始就会报错

java.lang.IndexOutOfBoundsException: Index: 0, Size: 0 
at java.util.ArrayList.rangeCheck(ArrayList.java:653) 
at java.util.ArrayList.remove(ArrayList.java:492) 
at TestWhileWaitBuf.get(TestWhileWait.java:80)atTestWhileWaitBuf.get(TestWhileWait.java:80)atTestWhileWait2.run(TestWhileWait.java:47) 
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) 
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) 
at java.lang.Thread.run(Thread.java:745)

很明显,在remove’的时候报错了. 
那么我们来分析下: 
假设现在有A, B两个线程来执行get 操作, 我们假设如下的步骤发生了: 
1. A 拿到了锁 line 0 
2. A 发现size==0, (line 1), 然后进入等待,并释放锁 (line 2) 
3. 此时B拿到了锁, line0, 发现size==0, (line 1), 然后进入等待,并释放锁 (line 2) 
4. 这个时候有个线程C往里面加了个数据1, 那么 notifyAll 所有的等待的线程都被唤醒了. 
5. AB 重新获取锁, 假设 又是A拿到了. 然后 他就走到line 3, 移除了一个数据, (line4) 没有问题. 
6. A 移除数据后 想通知别人, 此时list的大小有了变化, 于是调用了notifyAll (line5), 这个时候就把B给唤醒了, 那么B接着往下走. 
7. 这时候B就出问题了, 因为 其实 此时的竞态条件已经不满足了 (size==0). B以为还可以删除就尝试去删除, 结果就跑了异常了.

那么fix很简单, 在get的时候加上while就好了:

      synchronized int get() throws InterruptedException {while (list.size() == 0) {wait();}int v = list.remove(0);notifyAll();return v;}

同样的, 我们可以尝试修改put的线程数 和 get的线程数来 发现如果put里面不是while的话 也是不行的: 
我们可以用一个外部周期性任务来打印当前list的大小, 你会发现大小并不是固定的最大5:

        final Buf buf = new Buf();ExecutorService es = Executors.newFixedThreadPool(11);ScheduledExecutorService printer = Executors.newScheduledThreadPool(1);printer.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {System.out.println(buf.size());}}, 0, 1, TimeUnit.SECONDS);for (int i = 0; i < 10; i++)es.execute(new Runnable() {@Overridepublic void run() {while (true ) {try {buf.put(1);Thread.sleep(200);}catch (InterruptedException e) {e.printStackTrace();break;}}}});for (int i = 0; i < 1; i++) {es.execute(new Runnable() {@Overridepublic void run() {while (true ) {try {buf.get();Thread.sleep(100);}catch (InterruptedException e) {e.printStackTrace();break;}}}});}es.shutdown();es.awaitTermination(1, TimeUnit.DAYS);

这里 我想应该说清楚了为啥必须是while 还是if了

问题2:什么时候用notifyAll或者notify

大多数人都会这么告诉你: 
当你想要通知所有人的时候就用notifyAll, 当你只想通知一个人的时候就用notify. 
但是我们都知道notify实际上我们是没法决定到底通知谁的(都是从等待集合里面选一个). 那这个还有什么存在的意义呢?

在上面的例子中,我们用到了notifyAll, 那么下面我们来看下用notify是否可以工作呢? 
那么代码变成下面的样子:

     synchronized void put(int v) throws InterruptedException {if (list.size() == MAX) {wait();}list.add(v);notify();}synchronized int get() throws InterruptedException {while (list.size() == 0) {wait();}int v = list.remove(0);notify();return v;}

下面的几点是jvm告诉我们的:

  1. 任何时候,被唤醒的来执行的线程是不可预知. 比如有5个线程都在一个对象上, 实际上我不知道 下一个哪个线程会被执行.
  2. synchronized语义实现了有且只有一个线程可以执行同步块里面的代码.

那么我们假设下面的场景就会导致死锁: 
P - 生产者 调用put 
C - 消费者 调用get

  1. P1 放了一个数字1
  2. P2 想来放,发现满了,在wait里面等了
  3. P3 想来放,发现满了,在wait里面等了
  4. C1想来拿, C2, C3 就在get里面等着
  5. C1开始执行, 获取1, 然后调用notify 然后退出 
    如果C1把C2唤醒了, 所以P2 (其他的都得等.)只能在put方法上等着. (等待获取synchoronized (this) 这个monitor) 
    C2 检查while循环 发现此时队列是空的, 所以就在wait里面等着 
    C3 也比P2先执行, 那么发现也是空的, 只能等着了.
  6. 这时候我们发现P2 , C2, C3 都在等着锁. 最终P2 拿到了锁, 放一个1, notify,然后退出.
  7. P2 这个时候唤醒了P3, P3发现队列是满的,没办法,只能等它变为空.
  8. 这时候, 没有别的调用了, 那么现在这三个线程(P3, C2,C3)就全部变成suspend了.也就是死锁了.

Reference: 
http://stackoverflow.com/questions/37026/java-notify-vs-notifyall-all-over-again

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/327396.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java并发编程:Lock

转载自 Java并发编程&#xff1a;Lock 一.synchronized的缺陷 synchronized是java中的一个关键字&#xff0c;也就是说是Java语言内置的特性。那么为什么会出现Lock呢&#xff1f; 在上面一篇文章中&#xff0c;我们了解到如果一个代码块被synchronized修饰了&#xff0c;当一个…

docker下安装nacos 并使用mysql数据库

没做出来 腾讯云 有机会用虚拟机试试 参考资料 https://www.e-learn.cn/content/java/2357340 https://blog.csdn.net/xieqing_xq/article/details/105458887 重点 https://www.cnblogs.com/liushuchen/p/12488366.html 重点 http://122.51.187.132:8845/nacos/#/c…

javaMai+Springl实现给QQ邮箱发邮件(带附件,html格式)

以前的时候想着java发邮件很简单&#xff0c;因为当时使用的是outlook实现的&#xff0c;有兴趣的可以去看看之前的两篇博客文章&#xff0c;1.使用java底层实现邮件的发送&#xff08;含测试&#xff0c;源码&#xff09; 和 2.使用Spring实现邮件的发送&#xff08;含测试&…

分析开源项目

手把手教你分析开源项目 不知道代码怎么来的&#xff1f; 代码跑不起来&#xff1f; 项目对自己有帮助&#xff0c;不会模块化分析&#xff1f; 任何一个开源项目&#xff0c;都可以让自己得到提升&#xff01; 这里以 EL-ADMIN 为例https://el-admin.vip/ 1、观察开源项目…

Hangfire项目实践分享

项目中使用Hangfire已经快一年了&#xff0c;期间经历过很多次的试错及升级优化&#xff0c;才达到现在的稳定效果。趁最近不是太忙&#xff0c;自己在github上做了个案列&#xff0c;也是拿来跟大家分享下&#xff0c;案例是从项目里剥离出来的&#xff0c;有兴趣的可以访问 这…

IDEA开启Run Dashboard窗口

https://www.jianshu.com/p/df201a16d2cc 启动多个端口Run Dashboard窗口显得更好管理&#xff0c;如下&#xff1a; 图片 如果新建的项目运行后不出现Run Dashboard&#xff0c;希望切换成Run Dashboard运行需要进行手动修改。 首先在项目目录下的.idea 文件夹下的workspace…

如何修改服务器mac地址,如何修改服务器mac地址

如何修改服务器mac地址 内容精选换一换更新弹性云服务器的系统或者软件时&#xff0c;可以连接Internet&#xff0c;通过外部Pypi镜像源提供相关服务。但是&#xff0c;如果弹性云服务器无法访问Internet&#xff0c;或者外部Pypi镜像源提供的服务不稳定时&#xff0c;可以使用…

支持断线重连、永久watcher、递归操作并且能跨平台(.NET Core)的ZooKeeper异步客户端

什么是ZooKeeper&#xff1f; ZooKeeper是一个分布式的&#xff0c;开放源码的分布式应用程序协调服务&#xff0c;是Google的Chubby一个开源的实现&#xff0c;是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件&#xff0c;提供的功能包括&#xff1a;配…

云服务器的购买和宝塔面板的使用

为什么程序员都需要一个自己的服务器 1、作为一个程序员&#xff0c;必须要发布自己的网站和项目 2、练习Linux操作 3、自己的远程仓库、远程数据库、远程tomcat…搭建在服务器上 4、练习&#xff0c;Linux进行任意的环境部署操作 服务器如何购买 尽量打折的时候买 香港服务…

Java 多线程 —— 深入理解 volatile 的原理以及应用

转载自 Java 多线程 —— 深入理解 volatile 的原理以及应用 推荐阅读&#xff1a;《java 多线程—线程怎么来的》 这一篇主要讲解一下volatile的原理以及应用&#xff0c;想必看完这一篇之后&#xff0c;你会对volatile的应用原理以及使用边界会有更深刻的认知。本篇主要内容…

中间件和微服务,Docker以及原生云架构的关系

IT世界的技术更新非常迅速。一年前我曾写过一篇关于&#xff1a;微服务是否是企业服务总线和其他中间件的死亡魔法。本文章是之前文章的后续以及关于微服务、容器和原生云架构的中间件关系讨论的更新。各种规模的企业正在以令人不可思议的速度快速向这些技术靠拢&#xff01; 在…

linux安装jdk8

https://blog.csdn.net/pdsu161530247/article/details/81582980 linux安装jdk8 最后d轻语 2018-08-11 09:25:21 38205 收藏 83 分类专栏&#xff1a; 软件安装 文章标签&#xff1a; centos6安装jdk linux安装jdk 源码包安装 centos6.4 jdk8 版权 目录 1.下载jdk8 2.源…

.net线程池内幕

本文通过对.NET4.5的ThreadPool源码的分析讲解揭示.NET线程池的内幕&#xff0c;并总结ThreadPool设计的好与不足。 线程池的作用线程池&#xff0c;顾名思义&#xff0c;线程对象池。Task和TPL都有用到线程池&#xff0c;所以了解线程池的内幕有助于你写出更好的程序。由于篇幅…

Linux下安装nginx (tar解压版安装) nginx1.16.1

https://blog.csdn.net/qq_40431100/article/details/104729504 Linux下安装nginx (tar解压版安装) nginx1.16.1 Jkcc 2020-03-08 16:42:30 2241 收藏 分类专栏&#xff1a; linux 运行环境 文章标签&#xff1a; linux nginx 版权 Linux下安装nginx (tar安装) nginx1.16.…

ASP.NET Core HTTP 管道中的那些事儿

前言 马上2016年就要过去了&#xff0c;时间可是真快啊。 上次写完 Identity 系列之后&#xff0c;反响还不错&#xff0c;所以本来打算写一个 ASP.NET Core 中间件系列的&#xff0c;但是中间遇到了很多事情。首先是 NPOI 的移植工作&#xff0c;移植过后还有一些Bug需要修复&…

基本属性---Linux

基本属性 看懂文件属性 Linux系统是一种典型的多用户系统&#xff0c;不同的用户处于不同的地位&#xff0c;拥有不同的权限。为了保护系统的安全性&#xff0c;Linux系统对不同的用户访问同一文件&#xff08;包括目录文件&#xff09;的权限做了不同的规定。 在Linux中我们…

Prometheus 系统监控方案

最近一直在折腾时序类型的数据库&#xff0c;经过一段时间项目应用&#xff0c;觉得十分不错。而Prometheus又是刚刚推出不久的开源方案&#xff0c;中文资料较少&#xff0c;所以打算写一系列应用的实践过程分享一下。 Prometheus 是什么&#xff1f; Prometheus是一套开源的监…

怎样批量获取文件名,批量提取文件名 文件名读取windows 批处理文件

https://jingyan.baidu.com/article/cdddd41cb0776f53cb00e1e4.html https://jingyan.baidu.com/article/cdddd41cb0776f53cb00e1e4.html 如图&#xff0c;这个文件夹中有一些名字比较奇特的文件&#xff0c;接着我们就开始获取这些文件的文件名。 如图&#xff0c;文件在一…

大三那年在某宝8块钱买的.NET视频决定了我的职业生涯

前言 谨以此文献给那些还在大学中迷茫的莘莘学子们&#xff01; 韩愈在《师说》中提出了作为师者应该做的三件事&#xff1a;传道、授业、解惑。 1.传道&#xff1a;培养学生的道德观 2.授业&#xff1a;传授学生专业技能 3.解惑&#xff1a;解答学生内心的迷茫迷惑 曾几何时&a…

为什么说Java中只有值传递(另一种角度)

转载自 为什么说Java中只有值传递 对于初学者来说&#xff0c;要想把这个问题回答正确&#xff0c;是比较难的。在第二天整理答案的时候&#xff0c;我发现我竟然无法通过简单的语言把这个事情描述的很容易理解&#xff0c;遗憾的是&#xff0c;我也没有在网上找到哪篇文章可以…