1. 首页
  2. IT资讯

阿里面试题,为什么wait()方法要放在同步块中?

“u003Cpu003E某天我在***的时候,突然有个小伙伴微信上说:“哥,阿里面试又又挂了,被问到为什么wait()方法要放在同步块中,没答出来!”u003Cbru002Fu003Eu003Cu002Fpu003Eu003Cdiv class=”pgc-img”u003Eu003Cimg src=”http:u002Fu002Fp3.pstatp.comu002Flargeu002Fpgc-imageu002Fa132bf35158d4eb0a188e367ba3eadd4″ img_width=”1080″ img_height=”666″ alt=”阿里面试题,为什么wait()方法要放在同步块中?” inline=”0″u003Eu003Cp class=”pgc-img-caption”u003Eu003Cu002Fpu003Eu003Cu002Fdivu003Eu003Cpu003E我顿时觉得**一紧,仔细回顾一下,如果wait()方法不在同步块中,代码的确会抛出异常:u003Cu002Fpu003Eu003Cpreu003Epublic class WaitInSyncBlockTest {u003Cbru003E @Test public void test() { try { new Object().wait(); } catch (InterruptedException e) { e.printStackTrace(); } }}u003Cu002Fpreu003Eu003Cpu003E结果是:u003Cu002Fpu003Eu003Cdiv class=”pgc-img”u003Eu003Cimg src=”http:u002Fu002Fp3.pstatp.comu002Flargeu002Fpgc-imageu002Fb4d6bbb060c44e69b6daa20edb9ebba3″ img_width=”1080″ img_height=”151″ alt=”阿里面试题,为什么wait()方法要放在同步块中?” inline=”0″u003Eu003Cp class=”pgc-img-caption”u003Eu003Cu002Fpu003Eu003Cu002Fdivu003Eu003Cpu003E但是,为毛呢??我也没去了解过。u003Cu002Fpu003Eu003Cpu003E机智如我立刻假装正在开会忙得不可开交,回了一条:“开会中,等会和你细说。”u003Cu002Fpu003Eu003Cdiv class=”pgc-img”u003Eu003Cimg src=”http:u002Fu002Fp1.pstatp.comu002Flargeu002Fpgc-imageu002F5ff46f0d23274d44a618b478686e49e4″ img_width=”976″ img_height=”574″ alt=”阿里面试题,为什么wait()方法要放在同步块中?” inline=”0″u003Eu003Cp class=”pgc-img-caption”u003Eu003Cu002Fpu003Eu003Cu002Fdivu003Eu003Cpu003E经过一番谷歌之后,找到了答案。u003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003ELost Wake-Up Problemu003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E事情得从一个多线程编程里面臭名昭著的问题"Lost wake-up problem"说起。u003Cu002Fpu003Eu003Cpu003E这个问题并不是说只在Java语言中会出现,而是会在所有的多线程环境下出现。u003Cu002Fpu003Eu003Cpu003E假如有两个线程,一个消费者线程,一个生产者线程。生产者线程的任务可以简化成将count加一,而后唤醒消费者;消费者则是将count减一,而后在减到0的时候陷入睡眠:u003Cu002Fpu003Eu003Cpu003E生产者伪代码:u003Cu002Fpu003Eu003Cpreu003Ecount+1;notify();u003Cu002Fpreu003Eu003Cpu003E消费者伪代码:u003Cu002Fpu003Eu003Cpreu003Ewhile(count<=0) wait()u003Cbru003Ecount–u003Cu002Fpreu003Eu003Cpu003E熟悉多线程的朋友一眼就能够看出来,这里面有问题。什么问题呢?u003Cu002Fpu003Eu003Cpu003E生产者是两个步骤:u003Cu002Fpu003Eu003Col start=”1″u003Eu003Cliu003Ecount+1;u003Cu002Fliu003Eu003Cliu003Enotify();u003Cu002Fliu003Eu003Cu002Folu003Eu003Cpu003E消费者也是两个步骤:u003Cu002Fpu003Eu003Col start=”1″u003Eu003Cliu003E检查count值;u003Cu002Fliu003Eu003Cliu003E睡眠或者减一;u003Cu002Fliu003Eu003Cu002Folu003Eu003Cpu003E万一这些步骤混杂在一起呢?比如说,初始的时候count等于0,这个时候消费者检查count的值,发现count小于等于0的条件成立;就在这个时候,发生了上下文切换,生产者进来了,噼噼啪啪一顿操作,把两个步骤都执行完了,也就是发出了通知,准备唤醒一个线程。这个时候消费者刚决定睡觉,还没睡呢,所以这个通知就会被丢掉。紧接着,消费者就睡过去了……u003Cu002Fpu003Eu003Cdiv class=”pgc-img”u003Eu003Cimg src=”http:u002Fu002Fp1.pstatp.comu002Flargeu002Fpgc-imageu002F616de08f12e041cd9b296f5823d04d2f” img_width=”1080″ img_height=”975″ alt=”阿里面试题,为什么wait()方法要放在同步块中?” inline=”0″u003Eu003Cp class=”pgc-img-caption”u003Eu003Cu002Fpu003Eu003Cu002Fdivu003Eu003Cpu003E这就是所谓的lost wake up问题。u003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003E那么怎么解决这个问题呢?u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E现在我们应该就能够看到,问题的根源在于,消费者在检查count到调用wait()之间,count就可能被改掉了。u003Cu002Fpu003Eu003Cpu003E这就是一种很常见的竞态条件。u003Cu002Fpu003Eu003Cpu003E很自然的想法是,让消费者和生产者竞争一把锁,竞争到了的,才能够修改count的值。u003Cu002Fpu003Eu003Cpu003E于是生产者的代码是:u003Cu002Fpu003Eu003Cpreu003EtryLock()u003Cbru003Ecount+1u003Cbru003Enotify()u003Cbru003EreleaseLock()u003Cu002Fpreu003Eu003Cpu003E消费者的代码是:u003Cu002Fpu003Eu003Cpreu003EtryLock()u003Cbru003Ewhile(count <= 0) wait()u003Cbru003Ecount-1u003Cbru003EreleaseLocku003Cu002Fpreu003Eu003Cpu003E注意的是,我这里将两者的两个操作都放进去了同步块中。u003Cu002Fpu003Eu003Cpu003E现在来思考一个问题,生产者代码这样修改行不行?u003Cu002Fpu003Eu003Cpreu003EtryLock()u003Cbru003Ecount+1u003Cbru003Enotify()u003Cbru003EreleaseLock()u003Cu002Fpreu003Eu003Cpu003E答案是,这样改毫无卵用,依旧会出现lost wake up问题,而且和无锁的表现是一样的。u003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003E终极答案u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E所以,我们可以总结到,为了避免出现这种lost wake up问题,在这种模型之下,总应该将我们的代码放进去的同步块中。u003Cu002Fpu003Eu003Cpu003EJava强制我们的wait()u002Fnotify()调用必须要在一个同步块中,就是不想让我们在不经意间出现这种lost wake up问题。u003Cu002Fpu003Eu003Cpu003E不仅仅是这两个方法,包括java.util.concurrent.locks.Condition的await()u002Fsignal()也必须要在同步块中:u003Cu002Fpu003Eu003Cpreu003E private ReentrantLock lock = new ReentrantLock();u003Cbru003E private Condition condition = lock.newCondition();u003Cbru003E @Test public void test() { try { condition.signal(); } catch (Exception e) { e.printStackTrace(); } }u003Cu002Fpreu003Eu003Cdiv class=”pgc-img”u003Eu003Cimg src=”http:u002Fu002Fp1.pstatp.comu002Flargeu002Fpgc-imageu002F046f1dbb670e4cefb715042c805bb020″ img_width=”1080″ img_height=”150″ alt=”阿里面试题,为什么wait()方法要放在同步块中?” inline=”0″u003Eu003Cp class=”pgc-img-caption”u003Eu003Cu002Fpu003Eu003Cu002Fdivu003Eu003Cpu003E准确的来说,即便是我们自己在实现自己的锁机制的时候,也应该要确保类似于wait()和notify()这种调用,要在同步块内,防止使用者出现lost wake up问题。u003Cu002Fpu003Eu003Cpu003EJava的这种检测是很严格的。它要求的是,一定要处于锁对象的同步块中。举例来说:u003Cu002Fpu003Eu003Cpreu003E private Object obj = new Object();u003Cbru003E private Object anotherObj = new Object();u003Cbru003E @Test public void produce() { synchronized (obj) { try { anotherObj.notify(); } catch (Exception e) { e.printStackTrace(); } } }u003Cu002Fpreu003Eu003Cpu003E这样是没有什么卵用的。一样出现IllegalMonitorStateException。u003Cu002Fpu003Eu003Cpu003E可以拿去套路面试官的话术u003Cu002Fpu003Eu003Cpu003E到这里,按照道理来说,就可以结束了。不过既然是面试遇到的问题,我就提供点面试回答的小技巧。u003Cu002Fpu003Eu003Cpu003E假如面试官问你这个问题了,你最开始不要巴啦啦全部说出来。只需要轻描淡写地说:“这是Java设计者为了避免使用者出现lost wake up问题而搞出来的。”u003Cu002Fpu003Eu003Cpu003E注意演技,一定要轻描淡写中透露着一丝“我其实就知道lost wake up这个名词,再问就要露馅了”的感觉。u003Cu002Fpu003Eu003Cpu003E于是面试官肯定会追问:“lost wake up问题是什么?”u003Cu002Fpu003Eu003Cpu003E这个时候你就可以巴啦啦一大堆了。这个过程你要充满自信,表露出那种睥睨天下这种小问题就别来烦我的气概来。u003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003E于是,小手一抖,offer到手。u003Cu002Fstrongu003Eu003Cu002Fpu003E”

原文始发于:阿里面试题,为什么wait()方法要放在同步块中?

主题测试文章,只做测试使用。发布者:程序员,转转请注明出处:http://www.cxybcw.com/26428.html

联系我们

13687733322

在线咨询:点击这里给我发消息

邮件:1877088071@qq.com

工作时间:周一至周五,9:30-18:30,节假日休息

QR code