引言
這個東西是我今天看多線程通信的時候無意中想到的,為什麼像wait()、notify()、notifyAll()之類的線程間通信需要放在同步塊種,換言之為什麼要用synchronized。jie如果wait()方法不在同步塊中,會怎麼樣嘞:
<code>@Test
public void test() {
try {
new Object().wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}/<code>
結果是:
經過一番谷歌,參照了各路大神的博客,終於找到了答案。
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()會這樣
這就是所謂的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>
作者:ZARD007
鏈接:https://juejin.im/post/5e6a4d8a6fb9a07cd80f36d1
閱讀更多 Java技術前沿 的文章