## 關于問題
我在工作的時候,有一位組員問題一個問題:如果wait()方法不放在同步代碼塊會怎樣旬薯?
我馬上要開會忙得不可開交绊序,只是回答了一句話:”規(guī)定“。
等到有時間了骤公,我仔細回顧下,如果wait()方法不在同步塊中凌节,代碼的確會拋出IllegalMonitorStateException:
```java
? ? @Test
? ? public void test() {
? ? ? ? try {
? ? ? ? ? ? new Object().wait();
? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? }
```
![](https://upload-images.jianshu.io/upload_images/15590149-a35fb5ea3eb6890c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
但是洒试,為毛呢垒棋??為毛呢叼架?我也不知道啊碉碉,經過一番查閱,我找到了答案垢粮。
## Lost Wake-Up Problem
事情得從一個多線程編程里面臭名昭著的問題"Lost wake-up problem"說起。
這個問題并不是說只在Java語言中會出現(xiàn)毫蚓,而是會在所有的多線程環(huán)境下出現(xiàn)昔善。
假如我們有兩個線程,一個消費者線程翩概,一個生產者線程。生產者線程的任務可以簡化成先將count加一牍鞠,而后喚醒消費者评姨;消費者則是先將count減一,而后在減到0的時候陷入睡眠:
生產者偽代碼:
```swift
count+1;
notify();
```
消費者偽代碼:
```swift
while(count<=0)
? wait()
count--
```
這里面有問題胁后。什么問題呢嗦枢?
生產者是兩個步驟:
1. count+1;
2. notify();
消費者也是兩個步驟:
1. 檢查count值;
2. 睡眠或者減一敲才;
如果這些步驟混在一起會怎樣呢择葡?
比如說剃氧,初始的時候count等于0,這個時候消費者檢查count的值已添,發(fā)現(xiàn)count小于等于0的條件成立滥酥;就在這個時候,發(fā)生了上下文切換缆蝉,生產者進來了瘦真,噼噼啪啪一頓操作,把兩個步驟都執(zhí)行完了原杂,也就是發(fā)出了通知您机,準備喚醒一個線程年局。這個時候消費者剛決定睡覺咸产,還沒睡呢锐朴,所以這個通知就會被丟掉。緊接著焚志,消費者就睡過去了酱酬,消費者成睡美人了。
![](https://upload-images.jianshu.io/upload_images/15590149-cc87b208e0455b7a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
這就是所謂的lost wake up問題汗菜。
## 嘗試解決
問題的根源在于挑社,消費者在檢查count到調用wait()之間,count就可能被改掉了痛阻。
常見的解決方式是加鎖阱当,讓消費者和生產者競爭一把鎖,競爭到了的弊添,才能夠修改count的值油坝。
我這里將兩者的兩個操作都放進去了同步塊中,于是生產者的代碼是:
```java
tryLock()
count+1
notify()
releaseLock()
```
消費者的代碼是:
```java
tryLock()
while(count <= 0)
? wait()
count-1
releaseLock()
```
但是這樣改后依舊會出現(xiàn)lost wake up問題彼水,而且和無鎖的表現(xiàn)是一樣的极舔。
## 最終解決
為了避免出現(xiàn)這種lost wake up問題,在這種模型之下盯桦,應該將我們的代碼放進去的同步塊中。
Java強制我們的wait()/notify()調用必須要在一個同步塊中贴膘,就是不想讓我們在不經意間出現(xiàn)這種lost wake up問題略号。
不僅僅是這兩個方法,包括java.util.concurrent.locks.Condition的await()/signal()也必須要在同步塊中:
```java
? ? private ReentrantLock lock = new ReentrantLock();
? ? private Condition condition = lock.newCondition();? ?
@Test
? ? public void test2() {
? ? ? ? try {
? ? ? ? ? ? condition.signal();
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? }
```
![](https://upload-images.jianshu.io/upload_images/15590149-46fa9fca22143cbf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
準確的來說突梦,即便是我們自己在實現(xiàn)自己的鎖機制的時候羽利,也應該要確保類似于wait()和notify()這種調用这弧,要在同步塊內,防止使用者出現(xiàn)lost wake up問題皇帮。
Java的這種檢測是很嚴格的户矢。它要求的是,一定要處于鎖對象的同步塊中梯浪。舉例來說:
```java
private Object obj = new Object();
? ? private Object anotherObj = new Object();
? ? @Test
? ? public void test3() {
? ? ? ? synchronized (obj) {
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? anotherObj.notify();
? ? ? ? ? ? } catch (Exception e) {
? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? }
? ? ? ? }
? ? }
```
![](https://upload-images.jianshu.io/upload_images/15590149-8689188d7ef8e006.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
這樣也是沒什么用的挂洛。一樣出現(xiàn)IllegalMonitorStateException眠砾。
## 提高面試技巧
假如面試官問你這個問題了褒颈,你不要一五一十的全部說出來。只需要輕描淡寫地說:“這是Java設計者為了避免使用者出現(xiàn)lost wake up問題而搞出來的”谷丸,其中的逼格大家自己去體會刨疼。