為什么不用Wait和Notify

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

根河網(wǎng)站建設(shè)公司創(chuàng)新互聯(lián)公司,根河網(wǎng)站設(shè)計(jì)制作,有大型網(wǎng)站制作公司豐富經(jīng)驗(yàn)。已為根河千余家提供企業(yè)網(wǎng)站建設(shè)服務(wù)。企業(yè)網(wǎng)站搭建\成都外貿(mào)網(wǎng)站制作要多少錢,請(qǐng)找那個(gè)售后服務(wù)好的根河做網(wǎng)站的公司定做!

1.notify 線程“假死”

所謂的線程“假死”是指,在使用 notify  喚醒多個(gè)等待的線程時(shí),卻意外的喚醒了一個(gè)沒有“準(zhǔn)備好”的線程,從而導(dǎo)致整個(gè)程序進(jìn)入了阻塞的狀態(tài)不能繼續(xù)執(zhí)行。

以多線程編程中的經(jīng)典案例生產(chǎn)者和消費(fèi)者模型為例,我們先來演示一下線程“假死”的問題。

1.1 正常版本

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

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

接下來我們來創(chuàng)建兩個(gè)線程,一個(gè)是生產(chǎn)者調(diào)用 put 方法,另一個(gè)是消費(fèi)者調(diào)用 take 方法,實(shí)現(xiàn)代碼如下:

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

執(zhí)行結(jié)果如下:

為什么不用Wait和Notify

從上述結(jié)果可以看出,生產(chǎn)者和消費(fèi)者在循環(huán)交替的執(zhí)行任務(wù),場(chǎng)面非常和諧,是我們想要的正確結(jié)果。

1.2 線程“假死”版本

當(dāng)只有一個(gè)生產(chǎn)者和一個(gè)消費(fèi)者時(shí),wait 和 notify  方法不會(huì)有任何問題,然而**將生產(chǎn)者增加到兩個(gè)時(shí)就會(huì)出現(xiàn)線程“假死”的問題了,**程序的實(shí)現(xiàn)代碼如下:

public class NotifyDemo {     public static void main(String[] args) {   // 創(chuàng)建工廠方法(工廠類的代碼不變,這里不再?gòu)?fù)述)         Factory factory = new Factory();          // 生產(chǎn)者         Thread producer = new Thread(() -> {             try {                 factory.put();             } catch (InterruptedException e) {                 e.printStackTrace();             }         }, "生產(chǎn)者");         producer.start();          // 生產(chǎn)者 2         Thread producer2 = new Thread(() -> {             try {                 factory.put();             } catch (InterruptedException e) {                 e.printStackTrace();             }         }, "生產(chǎn)者2");         producer2.start();                  // 消費(fèi)者         Thread consumer = new Thread(() -> {             try {                 factory.take();             } catch (InterruptedException e) {                 e.printStackTrace();             }         }, "消費(fèi)者");         consumer.start();     } }

程序執(zhí)行結(jié)果如下:

為什么不用Wait和Notify

從以上結(jié)果可以看出,當(dāng)我們將生產(chǎn)者的數(shù)量增加到  2 個(gè)時(shí),就會(huì)造成線程“假死”阻塞執(zhí)行的問題,當(dāng)生產(chǎn)者 2 被喚醒又被阻塞之后,整個(gè)程序就不能繼續(xù)執(zhí)行了。

線程“假死”問題分析

我們先把以上程序的執(zhí)行步驟標(biāo)注一下,得到如下結(jié)果:

為什么不用Wait和Notify

從上圖可以看出:當(dāng)執(zhí)行到第 ④  步時(shí),此時(shí)生產(chǎn)者為工作狀態(tài),而生產(chǎn)者 2  和消費(fèi)者為等待狀態(tài),此時(shí)正確的做法應(yīng)該是喚醒消費(fèi)著進(jìn)行消費(fèi),然后消費(fèi)者消費(fèi)完之后再喚醒生產(chǎn)者繼續(xù)工作;但此時(shí)生產(chǎn)者卻錯(cuò)誤的喚醒了生產(chǎn)者 2,而生產(chǎn)者 2  因?yàn)殛?duì)列已經(jīng)滿了,所以自身并不具備繼續(xù)執(zhí)行的能力,因此就導(dǎo)致了整個(gè)程序的阻塞,流程圖如下所示:

為什么不用Wait和Notify

正確執(zhí)行流程應(yīng)該是這樣的:

為什么不用Wait和Notify

1.3 使用 Condition

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

  • await:對(duì)應(yīng) wait 方法;

  • signal:對(duì)應(yīng) notify 方法;

  • signalAll: notifyAll 方法。

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

// 創(chuàng)建 Condition 對(duì)象 Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); // 加鎖 lock.lock(); try {     // 業(yè)務(wù)方法....          // 1.進(jìn)入等待狀態(tài)     condition.await();      // 2.喚醒操作     condition.signal(); } catch (InterruptedException e) {     e.printStackTrace(); } finally {     lock.unlock(); }

小知識(shí):Lock的正確使用姿勢(shì)

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

回歸主題

回到本文的主題,我們?nèi)绻褂?Condition 來實(shí)現(xiàn)線程的通訊就可以避免程序的“假死”情況,因?yàn)?Condition  可以創(chuàng)建多個(gè)等待集,以本文的生產(chǎn)者和消費(fèi)者模型為例,我們可以使用兩個(gè)等待集,一個(gè)用做消費(fèi)者的等待和喚醒,另一個(gè)用來喚醒生產(chǎn)者,這樣就不會(huì)出現(xiàn)生產(chǎn)者喚醒生產(chǎn)者的情況了(生產(chǎn)者只能喚醒消費(fèi)者,消費(fèi)者只能喚醒生產(chǎn)者)這樣整個(gè)流程就不會(huì)“假死”了,它的執(zhí)行流程如下圖所示:

為什么不用Wait和Notify

了解了它的基本流程之后,咱們來看具體的實(shí)現(xiàn)代碼。

基于 Condition 的工廠實(shí)現(xiàn)代碼如下:

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

兩個(gè)生產(chǎn)者和一個(gè)消費(fèi)者的實(shí)現(xiàn)代碼如下:

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

程序的執(zhí)行結(jié)果如下圖所示:

為什么不用Wait和Notify

從上述結(jié)果可以看出,當(dāng)使用 Condition  時(shí),生產(chǎn)者、消費(fèi)者、生產(chǎn)者 2 會(huì)一直交替循環(huán)執(zhí)行,執(zhí)行結(jié)果符合我們的預(yù)期。

2.性能問題

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

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

以下是使用 wait 和 notifyAll 改進(jìn)后的代碼:

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

依舊是兩個(gè)生產(chǎn)者加一個(gè)消費(fèi)者,實(shí)現(xiàn)代碼如下:

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

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

為什么不用Wait和Notify

通過以上結(jié)果可以看出:當(dāng)我們調(diào)用  notifyAll  時(shí)確實(shí)不會(huì)造成線程“假死”了,但會(huì)造成所有的生產(chǎn)者都被喚醒了,但因?yàn)榇龍?zhí)行的任務(wù)只有一個(gè),因此被喚醒的所有生產(chǎn)者中,只有一個(gè)會(huì)執(zhí)行正確的工作,而另一個(gè)則是啥也不干,然后又進(jìn)入等待狀態(tài),這種行為對(duì)于整個(gè)程序來說,無疑是多此一舉,只會(huì)增加線程調(diào)度的開銷,從而導(dǎo)致整個(gè)程序的性能下降。

反觀 Condition 的 await 和 signal 方法,即使有多個(gè)生產(chǎn)者,程序也只會(huì)喚醒一個(gè)有效的生產(chǎn)者進(jìn)行工作,如下圖所示:

為什么不用Wait和Notify

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

到此,關(guān)于“為什么不用Wait和Notify”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!

網(wǎng)站欄目:為什么不用Wait和Notify
URL鏈接:http://muchs.cn/article28/ghspjp.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站設(shè)計(jì)、做網(wǎng)站建站公司、網(wǎng)站內(nèi)鏈Google、App開發(fā)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

成都定制網(wǎng)站建設(shè)