日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
為什么Wait和Notify必須放在Synchronized中?

wait/notify基礎(chǔ)使用

wait 和 notify 的基礎(chǔ)方法如下:

Object lock = new Object();
new Thread(() -> {
synchronized (lock) {
try {
System.out.println("wait 之前");
// 調(diào)用 wait 方法
lock.wait();
System.out.println("wait 之后");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();

Thread.sleep(100);
synchronized (lock) {
System.out.println("執(zhí)行 notify");
// 調(diào)用 notify 方法
lock.notify();
}

以上代碼的執(zhí)行結(jié)果如下圖所示:

wait/notify和synchronized一起用?

那問題來了,是不是 wait 和 notify 一定要配合 synchronized 一起使用呢?wait 和 notify 單獨使用行不行呢?我們嘗試將以上代碼中的 synchronized 代碼行刪除,實現(xiàn)代碼如下:

初看代碼好像沒啥問題,編譯器也沒報錯,好像能“正常使用”,然而當(dāng)我們運行以上程序時就會發(fā)生如下錯誤:

從上述結(jié)果可以看出:無論是 wait 還是 notify,如果不配合 synchronized 一起使用,在程序運行時就會報 IllegalMonitorStateException 非法的監(jiān)視器狀態(tài)異常,而且 notify 也不能實現(xiàn)程序的喚醒功能了。

原因分析

從上述的報錯信息我們可以看出,JVM 在運行時會強制檢查 wait 和 notify 有沒有在 synchronized 代碼中,如果沒有的話就會報非法監(jiān)視器狀態(tài)異常(IllegalMonitorStateException),但這也僅僅是運行時的程序表象,那為什么 Java 要這樣設(shè)計呢?其實這樣設(shè)計的原因就是為了防止多線程并發(fā)運行時,程序的執(zhí)行混亂問題。初看這句話,好像是用來描述“鎖”的。然而實際情況也是如此,wait 和 notify 引入鎖就是來規(guī)避并發(fā)執(zhí)行時程序的執(zhí)行混亂問題的。那這個“執(zhí)行混亂問題”到底是啥呢?接下來我們繼續(xù)往下看。

wait和notify問題復(fù)現(xiàn)

我們假設(shè) wait 和 notify 可以不加鎖,我們用它們來實現(xiàn)一個自定義阻塞隊列。這里的阻塞隊列是指讀操作阻塞,也就是當(dāng)讀取數(shù)據(jù)時,如果有數(shù)據(jù)就返回數(shù)據(jù),如果沒有數(shù)據(jù)則阻塞等待數(shù)據(jù),實現(xiàn)代碼如下:

class MyBlockingQueue {
// 用來保存數(shù)據(jù)的集合
Queue queue = new LinkedList<>();

/**
* 添加方法
*/
public void put(String data) {
// 隊列加入數(shù)據(jù)
queue.add(data);
// 喚醒線程繼續(xù)執(zhí)行(這里的線程指的是執(zhí)行 take 方法的線程)
notify(); // ③
}

/**
* 獲取方法(阻塞式執(zhí)行)
* 如果隊列里面有數(shù)據(jù)則返回數(shù)據(jù),如果沒有數(shù)據(jù)就阻塞等待數(shù)據(jù)
* @return
*/
public String take() throws InterruptedException {
// 使用 while 判斷是否有數(shù)據(jù)(這里使用 while 而非 if 是為了防止虛假喚醒)
while (queue.isEmpty()) { // ①
// 沒有任務(wù),先阻塞等待
wait(); // ②
}
return queue.remove(); // 返回數(shù)據(jù)
}
}

注意上述代碼,我們在代碼中標(biāo)識了三個關(guān)鍵執(zhí)行步驟:①:判斷隊列中是否有數(shù)據(jù);②:執(zhí)行 wait 休眠操作;③:給隊列中添加數(shù)據(jù)并喚醒阻塞線程。如果不強制要求添加 synchronized,那么就會出現(xiàn)如下問題:

步驟

線程1

線程2

1

執(zhí)行步驟 ① 判斷當(dāng)前隊列中沒有數(shù)據(jù)

2

執(zhí)行步驟 ③ 將數(shù)據(jù)添加到隊列,并喚醒線程1繼續(xù)執(zhí)行

3

執(zhí)行步驟 ② 線程 1 進入休眠狀態(tài)

從上述執(zhí)行流程看出問題了嗎?如果 wait 和 notify 不強制要求加鎖,那么在線程 1 執(zhí)行完判斷之后,尚未執(zhí)行休眠之前,此時另一個線程添加數(shù)據(jù)到隊列中。然而這時線程 1 已經(jīng)執(zhí)行過判斷了,所以就會直接進入休眠狀態(tài),從而導(dǎo)致隊列中的那條數(shù)據(jù)永久性不能被讀取,這就是程序并發(fā)運行時“執(zhí)行結(jié)果混亂”的問題。然而如果配合 synchronized 一起使用的話,代碼就會變成以下這樣:

class MyBlockingQueue {
// 用來保存任務(wù)的集合
Queue queue = new LinkedList<>();

/**
* 添加方法
*/
public void put(String data) {
synchronized (MyBlockingQueue.class) {
// 隊列加入數(shù)據(jù)
queue.add(data);
// 為了防止 take 方法阻塞休眠,這里需要調(diào)用喚醒方法 notify
notify(); // ③
}
}

/**
* 獲取方法(阻塞式執(zhí)行)
* 如果隊列里面有數(shù)據(jù)則返回數(shù)據(jù),如果沒有數(shù)據(jù)就阻塞等待數(shù)據(jù)
* @return
*/
public String take() throws InterruptedException {
synchronized (MyBlockingQueue.class) {
// 使用 while 判斷是否有數(shù)據(jù)(這里使用 while 而非 if 是為了防止虛假喚醒)
while (queue.isEmpty()) { // ①
// 沒有任務(wù),先阻塞等待
wait(); // ②
}
}
return queue.remove(); // 返回數(shù)據(jù)
}
}

這樣改造之后,關(guān)鍵步驟 ① 和關(guān)鍵步驟 ② 就可以一起執(zhí)行了,從而當(dāng)線程執(zhí)行了步驟 ③ 之后,線程 1 就可以讀取到隊列中的那條數(shù)據(jù)了,它們的執(zhí)行流程如下:

步驟

線程1

線程2

1

執(zhí)行步驟 ① 判斷當(dāng)前隊列沒有數(shù)據(jù)

2

執(zhí)行步驟 ② 線程進入休眠狀態(tài)

3

執(zhí)行步驟 ③ 將數(shù)據(jù)添加到隊列,并執(zhí)行喚醒操作

4

線程被喚醒,繼續(xù)執(zhí)行

5

判斷隊列中有數(shù)據(jù),返回數(shù)據(jù)

這樣咱們的程序就可以正常執(zhí)行了,這就是為什么 Java 設(shè)計一定要讓 wait 和 notify 配合上 synchronized 一起使用的原因了。

總結(jié)

本文介紹了 wait 和 notify 的基礎(chǔ)使用,以及為什么 wait 和 notify/notifyAll 一定要配合 synchronized 使用的原因。如果 wait 和 notify/notifyAll 不強制和 synchronized 一起使用,那么在多線程執(zhí)行時,就會出現(xiàn) wait 執(zhí)行了一半,然后又執(zhí)行了添加數(shù)據(jù)和 notify 的操作,從而導(dǎo)致線程一直休眠的缺陷。


網(wǎng)站欄目:為什么Wait和Notify必須放在Synchronized中?
文章出自:http://www.dlmjj.cn/article/dhpgjcj.html