面試官:為什麼wait( )方法要放在同步塊中?

引言

這個東西是我今天看多線程通信的時候無意中想到的,為什麼像wait()、notify()、notifyAll()之類的線程間通信需要放在同步塊種,換言之為什麼要用synchronized。jie如果wait()方法不在同步塊中,會怎麼樣嘞:

<code>@Test
public void test() {
try {
new Object().wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}/<code>

結果是:

面試官:為什麼wait( )方法要放在同步塊中?


經過一番谷歌,參照了各路大神的博客,終於找到了答案。

Lost Wake-Up Problem

首先我們來舉個例子,一個消費者線程、一個生產者線程。生產者的任務為count+1,然後喚醒消費者;消費者的任務為count-1,等到count為0時陷入沉睡。

生產者偽代碼:

<code>count++;
notify();/<code>

消費者偽代碼:

<code>while(count <= 0){
wait();
count--;
}/<code>

熟悉多線程的朋友應該一眼就看出來了問題,如果生產者和消費者的步驟混雜在一起會發生什麼。

首先我們先假設count = 0,這個時候消費者檢查count的值,發現count <= 0的條件成立;就在這個時候,發生了上下文切換,生產者進來了,噼噼啪啪一頓操作,把兩個步驟都執行完了,也就是發出了通知,準備喚醒一個線程。這個時候消費者剛決定睡覺,還沒睡呢,所以這個通知就會被丟掉。緊接著,消費者就睡過去了……

圖片來自為什麼wait()會這樣


面試官:為什麼wait( )方法要放在同步塊中?


這就是所謂的Lost Wake-Up Problem

如何解決

現在我們應該能發現問題的根源在於,消費者在檢查count到調用wait()之間,count就可能被改掉了。 那我們如何解決呢? 讓消費者和生產者競爭一把鎖,競爭到了的,才能夠修改count的值。

於是乎生產者代碼:

<code>lock();
count++;
notify();
unlock();/<code>

消費者代碼:

<code>lock();
while(count <= 0){
wait();
count--;
}
unlock();/<code>

現在我們來看看,這樣子真的解決了嗎?

答案是毫無卵用,依舊會出現lost wake up問題,而且和無鎖的表現是一樣的。

因為wait()是釋放鎖然後等待獲取鎖,當然要先獲得鎖才行,但在這邊連鎖都莫得。

終極答案

所以,我們可以總結到,為了避免出現這種 lost wake up 問題,在這種模型之下,總應該將我們的代碼放進去的同步塊中。

Java強制我們的 wait()/notify() 調用必須要在一個同步塊中,就是不想讓我們在不經意間出現這種 lost wake up 問題。

不僅僅是這兩個方法,包括 java.util.concurrent.locks.Condition 的 await()/signal() 也必須要在同步塊中。

正解:

<code>private Object obj = new Object();
private Object anotherObj = new Object();

@Test
public void produce() {
synchronized (obj) {
try {
//同步塊要對當前線程負責,而不是anotherObj.notify();
obj.notify();
} catch (Exception e) {
e.printStackTrace();
}
}
}/<code>

wait和notify的用法

wait()、notify()和notifyAll()

  • wait()、notify() 和 notifyAll()方法是本地方法,並且為 final 方法,無法被重寫。
  • 調用某個對象的 wait() 方法能讓當前線程阻塞,並且當前線程必須擁有此對象的 monitor(即鎖,或者叫管程)。
  • 調用某個對象的 notify() 方法能夠喚醒一個正在等待這個對象的 monitor 的線程,如果有多個線程都在等待這個對象的 monitor,則只能喚醒其中一個線程。
  • 調用 notifyAll() 方法能夠喚醒所有正在等待這個對象的monitor的線程。

具體應用

<code>
/**
* wait() && notify()方法
* 這兩個方法是在Object中定義的,用於協調線程同步,比 join 更加靈活
*/
public class NotifyDemo {
public static void main(String[] args) {
//寫兩個線程 1.圖片下載
Object obj=new Object();
Thread download=new Thread(){
public void run() {
System.out.println("開始下載圖片");
for (int i = 0; i < 101; i+=10) {
System.out.println("down"+i+"%");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("圖片下載成功");
synchronized (obj) {
obj.notify();//喚起
}
System.out.println("開始下載附件");
for (int i = 0; i < 101; i+=10) {
System.out.println("附件下載"+i+"%");

try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("附件下載成功");
}
};
//2.圖片展示
Thread show=new Thread(){
public void run(){
synchronized (obj) {
try {
obj.wait();//阻塞當前
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("show:開始展示圖片");
System.out.println("圖片展示完畢");
}

}
};
download.start();
show.start();
}
}/<code>


面試官:為什麼wait( )方法要放在同步塊中?


作者:ZARD007
鏈接:https://juejin.im/post/5e6a4d8a6fb9a07cd80f36d1


分享到:


相關文章: