91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

為什么不用Wait和Notify

發布時間:2021-10-21 16:27:18 來源:億速云 閱讀:155 作者:iii 欄目:web開發

這篇文章主要介紹“為什么不用Wait和Notify”,在日常操作中,相信很多人在為什么不用Wait和Notify問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”為什么不用Wait和Notify”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

1.notify 線程“假死”

所謂的線程“假死”是指,在使用 notify  喚醒多個等待的線程時,卻意外的喚醒了一個沒有“準備好”的線程,從而導致整個程序進入了阻塞的狀態不能繼續執行。

以多線程編程中的經典案例生產者和消費者模型為例,我們先來演示一下線程“假死”的問題。

1.1 正常版本

在演示線程“假死”的問題之前,我們先使用 wait 和 notify  來實現一個簡單的生產者和消費者模型,為了讓代碼更直觀,我這里寫一個超級簡單的實現版本。我們先來創建一個工廠類,工廠類里面包含兩個方法,一個是循環生產數據的(存入)方法,另一個是循環消費數據的(取出)方法,實現代碼如下。

/**  * 工廠類,消費者和生產者通過調用工廠類實現生產/消費  */ class Factory {     private int[] items = new int[1]; // 數據存儲容器(為了演示方便,設置容量最多存儲 1 個元素)     private int size = 0;             // 實際存儲大小      /**      * 生產方法      */     public synchronized void put() throws InterruptedException {         // 循環生產數據         do {             while (size == items.length) { // 注意不能是 if 判斷                 // 存儲的容量已經滿了,阻塞等待消費者消費之后喚醒                 System.out.println(Thread.currentThread().getName() + " 進入阻塞");                 this.wait();                 System.out.println(Thread.currentThread().getName() + " 被喚醒");             }             System.out.println(Thread.currentThread().getName() + " 開始工作");             items[0] = 1; // 為了方便演示,設置固定值             size++;             System.out.println(Thread.currentThread().getName() + " 完成工作");             // 當生產隊列有數據之后通知喚醒消費者             this.notify();          } while (true);     }      /**      * 消費方法      */     public synchronized void take() throws InterruptedException {         // 循環消費數據         do {             while (size == 0) {                 // 生產者沒有數據,阻塞等待                 System.out.println(Thread.currentThread().getName() + " 進入阻塞(消費者)");                 this.wait();                 System.out.println(Thread.currentThread().getName() + " 被喚醒(消費者)");             }             System.out.println("消費者工作~");             size--;             // 喚醒生產者可以添加生產了             this.notify();         } while (true);     } }

接下來我們來創建兩個線程,一個是生產者調用 put 方法,另一個是消費者調用 take 方法,實現代碼如下:

public class NotifyDemo {     public static void main(String[] args) {         // 創建工廠類         Factory factory = new Factory();          // 生產者         Thread producer = new Thread(() -> {             try {                 factory.put();             } catch (InterruptedException e) {                 e.printStackTrace();             }         }, "生產者");         producer.start();          // 消費者         Thread consumer = new Thread(() -> {             try {                 factory.take();             } catch (InterruptedException e) {                 e.printStackTrace();             }         }, "消費者");         consumer.start();     } }

執行結果如下:

為什么不用Wait和Notify

從上述結果可以看出,生產者和消費者在循環交替的執行任務,場面非常和諧,是我們想要的正確結果。

1.2 線程“假死”版本

當只有一個生產者和一個消費者時,wait 和 notify  方法不會有任何問題,然而**將生產者增加到兩個時就會出現線程“假死”的問題了,**程序的實現代碼如下:

public class NotifyDemo {     public static void main(String[] args) {   // 創建工廠方法(工廠類的代碼不變,這里不再復述)         Factory factory = new Factory();          // 生產者         Thread producer = new Thread(() -> {             try {                 factory.put();             } catch (InterruptedException e) {                 e.printStackTrace();             }         }, "生產者");         producer.start();          // 生產者 2         Thread producer2 = new Thread(() -> {             try {                 factory.put();             } catch (InterruptedException e) {                 e.printStackTrace();             }         }, "生產者2");         producer2.start();                  // 消費者         Thread consumer = new Thread(() -> {             try {                 factory.take();             } catch (InterruptedException e) {                 e.printStackTrace();             }         }, "消費者");         consumer.start();     } }

程序執行結果如下:

為什么不用Wait和Notify

從以上結果可以看出,當我們將生產者的數量增加到  2 個時,就會造成線程“假死”阻塞執行的問題,當生產者 2 被喚醒又被阻塞之后,整個程序就不能繼續執行了。

線程“假死”問題分析

我們先把以上程序的執行步驟標注一下,得到如下結果:

為什么不用Wait和Notify

從上圖可以看出:當執行到第 ④  步時,此時生產者為工作狀態,而生產者 2  和消費者為等待狀態,此時正確的做法應該是喚醒消費著進行消費,然后消費者消費完之后再喚醒生產者繼續工作;但此時生產者卻錯誤的喚醒了生產者 2,而生產者 2  因為隊列已經滿了,所以自身并不具備繼續執行的能力,因此就導致了整個程序的阻塞,流程圖如下所示:

為什么不用Wait和Notify

正確執行流程應該是這樣的:

為什么不用Wait和Notify

1.3 使用 Condition

為了解決線程的“假死”問題,我們可以使用 Condition 來嘗試實現一下,Condition 是  JUC(java.util.concurrent)包下的類,需要使用 Lock 鎖來創建,Condition 提供了 3 個重要的方法:

  • await:對應 wait 方法;

  • signal:對應 notify 方法;

  • signalAll: notifyAll 方法。

Condition 的使用和 wait/notify 類似,也是先獲得鎖然后在鎖中進行等待和喚醒操作,Condition 的基礎用法如下:

// 創建 Condition 對象 Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); // 加鎖 lock.lock(); try {     // 業務方法....          // 1.進入等待狀態     condition.await();      // 2.喚醒操作     condition.signal(); } catch (InterruptedException e) {     e.printStackTrace(); } finally {     lock.unlock(); }

小知識:Lock的正確使用姿勢

切記 Lock 的 lock.lock() 方法不能放入 try 代碼中,如果 lock 方法在 try  代碼塊之內,可能由于其它方法拋出異常,導致在 finally 代碼塊中, unlock 對未加鎖的對象解鎖,它會調用 AQS 的 tryRelease  方法(取決于具體實現類),拋出 IllegalMonitorStateException 異常。

回歸主題

回到本文的主題,我們如果使用 Condition 來實現線程的通訊就可以避免程序的“假死”情況,因為 Condition  可以創建多個等待集,以本文的生產者和消費者模型為例,我們可以使用兩個等待集,一個用做消費者的等待和喚醒,另一個用來喚醒生產者,這樣就不會出現生產者喚醒生產者的情況了(生產者只能喚醒消費者,消費者只能喚醒生產者)這樣整個流程就不會“假死”了,它的執行流程如下圖所示:

為什么不用Wait和Notify

了解了它的基本流程之后,咱們來看具體的實現代碼。

基于 Condition 的工廠實現代碼如下:

class FactoryByCondition {     private int[] items = new int[1]; // 數據存儲容器(為了演示方便,設置容量最多存儲 1 個元素)     private int size = 0;             // 實際存儲大小     // 創建 Condition 對象     private Lock lock = new ReentrantLock();     // 生產者的 Condition 對象     private Condition producerCondition = lock.newCondition();     // 消費者的 Condition 對象     private Condition consumerCondition = lock.newCondition();      /**      * 生產方法      */     public void put() throws InterruptedException {         // 循環生產數據         do {             lock.lock();             while (size == items.length) { // 注意不能是 if 判斷                 // 生產者進入等待                 System.out.println(Thread.currentThread().getName() + " 進入阻塞");                 producerCondition.await();                 System.out.println(Thread.currentThread().getName() + " 被喚醒");             }             System.out.println(Thread.currentThread().getName() + " 開始工作");             items[0] = 1; // 為了方便演示,設置固定值             size++;             System.out.println(Thread.currentThread().getName() + " 完成工作");             // 喚醒消費者             consumerCondition.signal();             try {             } finally {                 lock.unlock();             }         } while (true);     }      /**      * 消費方法      */     public void take() throws InterruptedException {         // 循環消費數據         do {             lock.lock();             while (size == 0) {                 // 消費者阻塞等待                 consumerCondition.await();             }             System.out.println("消費者工作~");             size--;             // 喚醒生產者             producerCondition.signal();             try {             } finally {                 lock.unlock();             }         } while (true);     } }

兩個生產者和一個消費者的實現代碼如下:

public class NotifyDemo {     public static void main(String[] args) {         FactoryByCondition factory = new FactoryByCondition();          // 生產者         Thread producer = new Thread(() -> {             try {                 factory.put();             } catch (InterruptedException e) {                 e.printStackTrace();             }         }, "生產者");         producer.start();          // 生產者 2         Thread producer2 = new Thread(() -> {             try {                 factory.put();             } catch (InterruptedException e) {                 e.printStackTrace();             }         }, "生產者2");         producer2.start();          // 消費者         Thread consumer = new Thread(() -> {             try {                 factory.take();             } catch (InterruptedException e) {                 e.printStackTrace();             }         }, "消費者");         consumer.start();     } }

程序的執行結果如下圖所示:

為什么不用Wait和Notify

從上述結果可以看出,當使用 Condition  時,生產者、消費者、生產者 2 會一直交替循環執行,執行結果符合我們的預期。

2.性能問題

在上面我們演示 notify 會造成線程的“假死”問題的時候,一定有朋友會想到,如果把 notify 換成 notifyAll  線程就不會“假死”了。

這樣做法確實可以解決線程“假死”的問題,但同時會到來新的性能問題,空說無憑,直接上代碼展示。

以下是使用 wait 和 notifyAll 改進后的代碼:

/**  * 工廠類,消費者和生產者通過調用工廠類實現生產/消費功能.  */ class Factory {     private int[] items = new int[1];   // 數據存儲容器(為了演示方便,設置容量最多存儲 1 個元素)     private int size = 0;               // 實際存儲大小      /**      * 生產方法      * @throws InterruptedException      */     public synchronized void put() throws InterruptedException {         // 循環生產數據         do {             while (size == items.length) { // 注意不能是 if 判斷                 // 存儲的容量已經滿了,阻塞等待消費者消費之后喚醒                 System.out.println(Thread.currentThread().getName() + " 進入阻塞");                 this.wait();                 System.out.println(Thread.currentThread().getName() + " 被喚醒");             }             System.out.println(Thread.currentThread().getName() + " 開始工作");             items[0] = 1; // 為了方便演示,設置固定值             size++;             System.out.println(Thread.currentThread().getName() + " 完成工作");             // 喚醒所有線程             this.notifyAll();         } while (true);     }      /**      * 消費方法      * @throws InterruptedException      */     public synchronized void take() throws InterruptedException {         // 循環消費數據         do {             while (size == 0) {                 // 生產者沒有數據,阻塞等待                 System.out.println(Thread.currentThread().getName() + " 進入阻塞(消費者)");                 this.wait();                 System.out.println(Thread.currentThread().getName() + " 被喚醒(消費者)");             }             System.out.println("消費者工作~");             size--;             // 喚醒所有線程             this.notifyAll();         } while (true);     } }

依舊是兩個生產者加一個消費者,實現代碼如下:

public static void main(String[] args) {     Factory factory = new Factory();     // 生產者     Thread producer = new Thread(() -> {         try {             factory.put();         } catch (InterruptedException e) {             e.printStackTrace();         }     }, "生產者");     producer.start();      // 生產者 2     Thread producer2 = new Thread(() -> {         try {             factory.put();         } catch (InterruptedException e) {             e.printStackTrace();         }     }, "生產者2");     producer2.start();      // 消費者     Thread consumer = new Thread(() -> {         try {             factory.take();         } catch (InterruptedException e) {             e.printStackTrace();         }     }, "消費者");     consumer.start(); }

執行的結果如下圖所示:

為什么不用Wait和Notify

通過以上結果可以看出:當我們調用  notifyAll  時確實不會造成線程“假死”了,但會造成所有的生產者都被喚醒了,但因為待執行的任務只有一個,因此被喚醒的所有生產者中,只有一個會執行正確的工作,而另一個則是啥也不干,然后又進入等待狀態,這種行為對于整個程序來說,無疑是多此一舉,只會增加線程調度的開銷,從而導致整個程序的性能下降。

反觀 Condition 的 await 和 signal 方法,即使有多個生產者,程序也只會喚醒一個有效的生產者進行工作,如下圖所示:

為什么不用Wait和Notify

生產者和生產者 2  依次會被交替的喚醒進行工作,所以這樣執行時并沒有任何多余的開銷,從而相比于 notifyAll 而言整個程序的性能會提升不少。

到此,關于“為什么不用Wait和Notify”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

诏安县| 涪陵区| 鄂尔多斯市| 金坛市| 扎囊县| 英吉沙县| 延庆县| 镇沅| 晴隆县| 乡城县| 象山县| 吴堡县| 成都市| 长汀县| 桃园市| 郧西县| 葵青区| 淮安市| 临泽县| 衢州市| 从江县| 布尔津县| 苏州市| 长葛市| 廉江市| 新源县| 浦江县| 集安市| 霍邱县| 建昌县| 北宁市| 凤城市| 肃南| 通城县| 夏津县| 大宁县| 台北县| 民县| 黎城县| 威远县| 介休市|