上一篇:2T架构师学习资料干货分享

问题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告诉我们的:想成为架构师,这份架构师图谱建议看看,少走弯路。

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

那么我们假设下面的场景就会导致死锁:

P - 生产者 调用put

C - 消费者 调用get

P1 放了一个数字1

P2 想来放,发现满了,在wait里面等了

P3 想来放,发现满了,在wait里面等了

C1想来拿, C2, C3 就在get里面等着

C1开始执行, 获取1, 然后调用notify 然后退出

如果C1把C2唤醒了, 所以P2 (其他的都得等.)只能在put方法上等着. (等待获取synchoronized (this) 这个monitor)

C2 检查while循环 发现此时队列是空的, 所以就在wait里面等着

C3 也比P2先执行, 那么发现也是空的, 只能等着了.

这时候我们发现P2 , C2, C3 都在等着锁. 最终P2 拿到了锁, 放一个1, notify,然后退出.

P2 这个时候唤醒了P3, P3发现队列是满的,没办法,只能等它变为空. 这时候, 没有别的调用了, 那么现在这三个线程(P3, C2,C3)就全部变成suspend了.也就是死锁了.

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

原文链接:https://blog.csdn.net/scugxl/article/details/71434083

感谢您的阅读,也欢迎您发表关于这篇文章的任何建议,关注我,技术不迷茫!小编到你上高速。

· END ·

最后,关注公众号互联网架构师,在后台回复:2T,可以获取我整理的 Java 系列面试题和答案,非常齐全。

正文结束

推荐阅读 ↓↓↓

1.救救大龄码农!45岁程序员在国务院网站求助总理!央媒网评来了...

2.如何才能成为优秀的架构师?

3.从零开始搭建创业公司后台技术栈

4.程序员一般可以从什么平台接私活?

5.37岁程序员被裁,120天没找到工作,无奈去小公司,结果懵了...

6.IntelliJ IDEA 2019.3 首个最新访问版本发布,新特性抢先看

7.这封“领导痛批95后下属”的邮件,句句扎心!

8.15张图看懂瞎忙和高效的区别!

面试官:你是如何调用 wait() 方法的?使用 if 还是 while?别答错了!相关推荐

  1. 面试官:同事欠你500,你怎么要回来?硕士答不上,学渣智答录用

    在日常生活中,难免会遇到同事借钱的情况,这时,如何把借出去的钱要回来,是一件既考验情商,又考验能力的技术活. 在面试中,遇到一些不按照套路出牌的面试官,便会考验直击人心的面试题,考验你的情商和应变能力 ...

  2. 【245期】面试官:同类中两个方法加同步锁,多个线程支持同时访问这两个方法吗?...

    点击上方"Java精选",选择"设为星标" 别问别人为什么,多问自己凭什么! 下方有惊喜,留言必回,有问必答! 每天 08:15 更新文章,每天进步一点点... ...

  3. 面试官:说一下List排序方法

    1. 前言 排序算是比较高频的面试题了,节前面试了的两家公司都有问到排序问题,整理后分享给大家(文末见总结). 通常我们想到实现排序就是 Collections 工具类的 sort() 方法,而 so ...

  4. 赋值给集合_当面试官问集合遍历的删除方法时,要警惕这里有个异常陷阱

    开发中有时需要在遍历集合的同时又对集合元素进行删除操作,如何正确的删除并了解为什么很有必要. 比如: 这里会触发并发修改异常: 抛出异常原因:modCount和expectedModCount值不相等 ...

  5. python 字符串拼接_面试官让用 3 种 python 方法实现字符串拼接 ?对不起我有8种……...

    点击上方 蓝字关注我们 点击上方"印象python",选择"星标"公众号重磅干货,第一时间送达!之前发过很多关于 Python 学习的文章,收到大家不少的好评, ...

  6. union distinct_当面试官问你UNION 和UNION ALL之间的区别时该怎么答?

    概述 MySQL数据库支持两种集合操作:UNION DISTINCT和UNION ALL. UNION DISTINCT组合两个输入,并应用DISTINCT过滤重复项,一般可以直接省略DISTINCT ...

  7. 面试官:HashMap源码看过吧,讲一讲put方法的源码是怎样实现的???

    前言 点赞在看,养成习惯. 点赞收藏,人生辉煌. 点击关注[微信搜索公众号:编程背锅侠],第一时间获得最新文章. HashMap系列文章 第一篇 HashMap源码中的成员变量你还不懂? 来来来!!! ...

  8. Spring框架你敢写精通,面试官就敢问@Autowired注解的实现原理

    面试官:Spring框架中的@Autowired注解可以标注在哪些地方? 小小白:@Autowired注解可以被标注在构造函数.属性.setter方法或配置方法上,用于实现依赖自动注入. 面试官:有没 ...

  9. 知乎高赞:如果你是一个 Java 面试官,你会问哪些问题....

    注:本文内容选自公众号<Java面试题精选>,内容比较丰富,帮助大家做面试前的准备,可以省不少时间.欢迎收藏点赞,也欢迎去围观原号主! 不断收集整理,汇总网上面试知识点,方便面试前刷题,希 ...

  10. 一气之下,我一行代码搞定了约瑟夫环问题,面试官懵了

    大家好,我是帅地. 对于约瑟夫环问题估计大家都听说过,除非你刚刚读大一,因为在大一大部分学校的课本都会降到这个算法题.为了以防万一你没听过,我还是给下问题的描述 问题描述:编号为 1-N 的 N 个士 ...

最新文章

  1. C++语言基础 例程 文本文件的读写
  2. 【视频课】StyleGAN人脸生成与年龄表情编辑:原理与实践
  3. mysql单表备份语句 +多表
  4. android获取string.xml的值
  5. Elementui动态换肤
  6. 新年礼物 总算有服务器了
  7. 玩转SpringBoot2.x之缓存对象
  8. 21天Jenkins打卡Day10-自动触发项目构建
  9. pycharm调试步骤(详细)
  10. 百度地图离线调用(详细教程)
  11. 【武汉加油!中国加油!】挑战七天 实现机器视觉检测有没有戴口罩系统——第四五六七天
  12. Safari插件机制研究(一)
  13. 科研成果 | 信道模型 | 原理及随机数仿真 | 均匀、正态、双高斯、瑞利、莱斯、对数正态、nakagami、Suzuki分布的随机数仿真(matlab)
  14. python合并excel的多个sheet
  15. 知否:高增长时代已过,汽车互联网玩家如何开拓更多增量?
  16. .NET CORE认证1.认识登陆和授权
  17. 非常有用的 windows CMD 命令大全
  18. 设文件索引结点中有7个地址项
  19. java的网络协议学习_协议简史:如何学习网络协议?
  20. 机器学习翻译任务中的constrain decoding 实现流程方法详解

热门文章

  1. node-js由浅入深教程
  2. 如何使用iMazing备份、恢复《暴力飞车》游戏存档
  3. 设计模式入门-工厂模式
  4. iOS开发特效源码:swift轮播图导航渐变跑马灯分段选择下拉菜单物流时间轴
  5. nodejs安装node-gyp 报错
  6. PoJ3278--Catch That Cow(Bfs)
  7. ViewHolder最简洁的写法
  8. [Node] 基础知识
  9. .Net中的数字和日期格式化规则助记词
  10. 博客改版日记9.7——内测先锋队总动员