某天我在***的时候,突然有个小伙伴微信上说:“哥,阿里面试又又挂了,被问到为什么wait()方法要放在同步块中,没答出来!”

我顿时觉得**一紧,仔细回顾一下,如果wait()方法不在同步块中,代码的确会抛出异常:

public void test(){
try{
new Object().wait();
}catch(InterruptedException e){
e.printStackTrace();
} }

结果是:

Exception in thread "main" java.lang.IllegalMonitorStateException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Unknown Source)
at com.dapeng.day413.WaitSyncBlockTest.test(WaitSyncBlockTest.java:16)
at com.dapeng.day413.WaitSyncBlockTest.main(WaitSyncBlockTest.java:40)

但是,为毛呢??我也没去了解过。

机智如我立刻假装正在开会忙得不可开交,回了一条:“开会中,等会和你细说。”

经过一番谷歌之后,找到了答案。

Lost Wake-Up Problem

事情得从一个多线程编程里面臭名昭著的问题"Lost wake-up problem"说起。

这个问题并不是说只在Java语言中会出现,而是会在所有的多线程环境下出现。

假如有两个线程,一个消费者线程,一个生产者线程。生产者线程的任务可以简化成将count加一,而后唤醒消费者;消费者则是将count减一,而后在减到0的时候陷入睡眠:

生产者伪代码:

count+1;
notify();

消费者伪代码:

while(count<=0)
wait();
count
--

熟悉多线程的朋友一眼就能够看出来,这里面有问题。什么问题呢?

生产者是两个步骤:

  1. count+1;

  2. notify();

消费者也是两个步骤:

  1. 检查count值;

  2. 睡眠或者减一;

万一这些步骤混杂在一起呢?比如说,初始的时候count等于0,这个时候消费者检查count的值,发现count小于等于0的条件成立;就在这个时候,发生了上下文切换,生产者进来了,噼噼啪啪一顿操作,把两个步骤都执行完了,也就是发出了通知,准备唤醒一个线程。这个时候消费者刚决定睡觉,还没睡呢,所以这个通知就会被丢掉。紧接着,消费者就睡过去了……

这就是所谓的lost wake up问题。

那么怎么解决这个问题呢?

现在我们应该就能够看到,问题的根源在于,消费者在检查count到调用wait()之间,count就可能被改掉了。

这就是一种很常见的竞态条件。

很自然的想法是,让消费者和生产者竞争一把锁,竞争到了的,才能够修改count的值。

于是生产者的代码是:

tryLock()
count+1
notify()
releaseLock()

消费者的代码是:

tryLock()
while(count <=0)
wait()
count-1
releaseLock

注意的是,我这里将两者的两个操作都放进去了同步块中。

现在来思考一个问题,生产者代码这样修改行不行?

tryLock()
count+1
notify()
releaseLock()

答案是,这样改毫无卵用,依旧会出现lost wake up问题,而且和无锁的表现是一样的。

终极答案

所以,我们可以总结到,为了避免出现这种lost wake up问题,在这种模型之下,总应该将我们的代码放进去的同步块中。

Java强制我们的wait()/notify()调用必须要在一个同步块中,就是不想让我们在不经意间出现这种lost wake up问题。

不仅仅是这两个方法,包括java.util.concurrent.locks.Condition的await()/signal()也必须要在同步块中:

private ReentrantLock lock = new ReentrantLock();

    private Condition condition = lock.newCondition();

    @Test
public void test() { try{
condition.signal(); }catch(Exception e){
e.printStackTrace(); } }
Exception in thread "main" java.lang.IllegalMonitorStateException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Unknown Source)
at com.dapeng.day413.WaitSyncBlockTest.test(WaitSyncBlockTest.java:16)
at com.dapeng.day413.WaitSyncBlockTest.main(WaitSyncBlockTest.java:40)

准确的来说,即便是我们自己在实现自己的锁机制的时候,也应该要确保类似于wait()和notify()这种调用,要在同步块内,防止使用者出现lost wake up问题。

Java的这种检测是很严格的。它要求的是,一定要处于锁对象的同步块中。举例来说:

private Object  obj = new Object ();
private Object anotherObj = new Object(); @Test
public void produce(){
synchronized(obj){
try{
anotherObj.notify();
}catch(Exception e){
e.printStackTrace();
}
}

这样是没有什么卵用的。一样出现IllegalMonitorStateException。

可以拿去套路面试官的话术

到这里,按照道理来说,就可以结束了。不过既然是面试遇到的问题,我就提供点面试回答的小技巧。

假如面试官问你这个问题了,你最开始不要巴啦啦全部说出来。只需要轻描淡写地说:“这是Java设计者为了避免使用者出现lost wake up问题而搞出来的。”

注意演技,一定要轻描淡写中透露着一丝“我其实就知道lost wake up这个名词,再问就要露馅了”的感觉。

于是面试官肯定会追问:“lost wake up问题是什么?”

这个时候你就可以巴啦啦一大堆了。这个过程你要充满自信,表露出那种睥睨天下这种小问题就别来烦我的气概来。

于是,小手一抖,offer到手。

原文转自:https://mp.weixin.qq.com/s/ohcr6T1aB7-lVFJIfyJZjA

阿里面试题,为什么wait()方法要放在同步块中?的更多相关文章

  1. 为什么wait()方法要放在同步块

    回顾一下,如果wait()方法不在同步块中,代码的确会抛出异常: public class WaitInSyncBlockTest { @Test public void test() { try { ...

  2. Java中wait()方法为什么要放在同步块中?(lost wake-up 问题)

    问题起源 事情得从一个多线程编程里面臭名昭著的问题"Lost wake-up problem"说起. 这个问题并不是说只在Java语言中会出现,而是会在所有的多线程环境下出现. 假 ...

  3. 为什么 wait()方法和 notify()/notifyAll()方法要在同步块 中被调用 ?

    这是 JDK 强制的,wait()方法和 notify()/notifyAll()方法在调用前都必须先获得对 象的锁

  4. 解释为什么wait()和notify(), notifyAll()要放在同步块中

    首先,wait()是释放锁的,因此wait()之前要先获得锁,而锁在同步块开始的时候获得,结束时释放,即同步块内为持有锁的阶段. 那为什么要设计同步块呢?或者说没有同步块会怎样呢?

  5. 为什么 wait 和 notify 方法要在同步块中调用?

    Java API 强制要求这样做,如果你不这么做,你的代码会抛出 IllegalMonitorStateException 异常.还有一个原因是为了避免 wait 和 notify 之间产生竞态条件.

  6. java中哪块代码或说什么代码应该放在try块中呢?

    我怎么知道哪块代码可能出现问题,从而放在try块儿中呢?马 克-to-win:一个笨办法,开始时,你并不加try,但你发现,运行时,用户赋给除数一个0,所以程序在这崩溃了,于是你就把这块代码加个try ...

  7. close()方法应该在finally语句中调用吗?

    翻译人员: 铁锚 翻译时间: 2013年12月20日 原文链接: Should .close() be put in finally block or not? 下面列出了关闭输出流(output w ...

  8. java 为什么wait(),notify(),notifyAll()必须在同步(Synchronized)方法/代码块中调用?

    wait()作用:该方法用来将当前线程置入休眠状态,直到接到通知或被中断为止.条件:在调用wait()之前,线程必须要获得该对象的对象级别锁,即只能在同步方法或同步块中调用wait()方法.进入wai ...

  9. @Required 注释应用于 bean 属性的 setter 方法,它表明受影响的 bean 属性在配置时必须放在 XML 配置文件中,否则容器就会抛出一个 BeanInitializationException 异常。

    @Required 注释应用于 bean 属性的 setter 方法,它表明受影响的 bean 属性在配置时必须放在 XML 配置文件中,否则容器就会抛出一个 BeanInitializationEx ...

随机推荐

  1. DEDECMS织梦后台更新网站栏目无反应一键更新无响应的解决方法

    很多站长朋友反应,经常会遇到DEDECMS织梦后台更新网站栏目无反应和一键更新无响应的问题,这个问题的所在就是在于恢复了数据或者覆盖了织梦后台文件之后,点击一键更新完全没反应,或者生成栏目的时候其他都 ...

  2. java_缓冲流(字节输入流)

    /** * java.iko.BufferedInputStream extends InputStream * BufferedInputStream:字节缓冲输入流 * 构造方法: * Buffe ...

  3. 移植 thttpd Web 服务器

    下载 从 http://www.acme.com/software/thttpd/下载 thttpd 到/tmp 目录当中,并解压. 编译 thttpd [arm@localhost thttpd­2 ...

  4. 如何将存储在MongoDB数据库中的数据导出到Excel中?

    将MongoDB数据库中的数据导出到Excel中,只需以下几个步骤: (1)首先,打开MongoDB安装目录下的bin文件夹,(C:\Program Files (x86)\MongoDB\Serve ...

  5. 介绍一下再Apache下的Tomcat负载均衡的一些使用问题

    在负载均衡技术中,硬件设备是比较昂贵的,对于负载均衡的学习者如果不是在企业中应用或者是学员中学习,很少有机会能碰到实际操作的训练.(http://xz.8682222.com)所以,很多朋友都会选择软 ...

  6. 廖雪峰Java14Java操作XML和JSON-1XML-2DOM

    XML是一种数据表示形式. 可以描述非常复杂的数据数据结构 用于传输和传输数据 DOM:Document Object Model DOM模型就是把XML文档作为一个树形结构,从根结点开始,每个节点都 ...

  7. 「题解」:windy数

    问题: windy数 时间限制: 1 Sec  内存限制: 512 MB 题面 题目描述 Windy 定义了一种 Windy 数:不含前导零且相邻两个数字之差至少为 的正整数被称为 Windy 数. ...

  8. Joomla - 部署(线上部署)

    一.线上部署 线上部署可以理解为把本地网站迁移到线上,使用 akeeba backup 进行备份和迁移即可 参考 Joomla - akeeba backup(joomla网站备份.迁移扩展)的第三. ...

  9. Windbg 问题集锦记录

    问题1: 问: 0  Id: 15f4.e60 Suspend: 1 Teb: 7ffdf000 Unfrozen  # ChildEBP RetAddr  Args to Child         ...

  10. OpenCASCADE点向直线投影

    OpenCASCADE点向直线投影 eryar@163.com 在GeomLib_Tool类中提供了计算指定点在曲线.曲面上的参数,这个算法具有通用性,即对任意曲线.曲面来反求点的参数. 本文主要结合 ...