日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

求求你,别再用wait和notify了!

發布時間:2025/3/11 编程问答 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 求求你,别再用wait和notify了! 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

作者 | 王磊

來源 | Java中文社群(ID:javacn666)

轉載請聯系授權(微信ID:GG_Stone)

Condition 是 JDK 1.5 中提供的用來替代 wait 和 notify 的線程通訊方法,那么一定會有人問:為什么不能用 wait 和 notify 了? 哥們我用的好好的。老弟別著急,聽我給你細說...

之所以推薦使用 Condition 而非 Object 中的 wait 和 notify?的原因有兩個:

  • 使用 notify 在極端環境下會造成線程“假死”;

  • Condition 性能更高。

  • 接下來怎們就用代碼和流程圖的方式來演示上述的兩種情況。

    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();} }

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

    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();//?生產者?2Thread?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();} }

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

    線程“假死”問題分析

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

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

    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?可以創建多個等待集,以本文的生產者和消費者模型為例,我們可以使用兩個等待集,一個用做消費者的等待和喚醒,另一個用來喚醒生產者,這樣就不會出現生產者喚醒生產者的情況了(生產者只能喚醒消費者,消費者只能喚醒生產者)這樣整個流程就不會“假死”了,它的執行流程如下圖所示:了解了它的基本流程之后,咱們來看具體的實現代碼。

    基于 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();//?生產者?2Thread?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();} }

    程序的執行結果如下圖所示:從上述結果可以看出,當使用 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();//?生產者?2Thread?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(); }

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

    反觀 Condition?的 await?和 signal?方法,即使有多個生產者,程序也只會喚醒一個有效的生產者進行工作,如下圖所示:生產者和生產者 2 依次會被交替的喚醒進行工作,所以這樣執行時并沒有任何多余的開銷,從而相比于 notifyAll?而言整個程序的性能會提升不少。

    總結

    本文我們通過代碼和流程圖的方式演示了 wait?方法和 notify/notifyAll?方法的使用缺陷,它的缺陷主要有兩個,一個是在極端環境下使用 notify?會造成程序“假死”的情況,另一個就是使用 notifyAll?會造成性能下降的問題,因此在進行線程通訊時,強烈建議使用 Condition?類來實現。

    PS:有人可能會問為什么不用 Condition 的 signalAll 和 notifyAll 進行性能對比?而使用 signal 和 notifyAll 進行對比?我只想說,既然使用 signal 可以實現此功能,為什么還要使用 signalAll 呢?這就好比在有暖氣的 25 度的房間里,穿一件短袖就可以了,為什么還要穿一件棉襖呢?

    往期推薦

    求求你,不要再使用!=null判空了!

    2020-12-01

    提高生產力,最全 MyBatisPlus 講解!

    2020-12-10

    2020年終總結:新的“開始”

    2020-12-11

    關注我,每天陪你進步一點點!

    總結

    以上是生活随笔為你收集整理的求求你,别再用wait和notify了!的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 国产精品一级片在线观看 | 超碰超在线 | 免费国产黄 | www网站在线免费观看 | 美女户外露出 | 日韩在线不卡av | 日本精品视频在线播放 | 欧美一区二区三区观看 | 色爱区综合 | 中文字幕日韩三级 | 国产成人精品一区二区三区在线 | 深爱开心激情网 | 国产精品第1页 | 日本天堂免费a | 色一情一区二区三区四区 | 欧美性开放视频 | 男人天堂视频在线观看 | 成人午夜在线 | 精品无码久久久久久久久果冻 | 麻豆欧美 | 在线观看亚洲精品 | 72种无遮挡啪啪的姿势 | 成人免费高清视频 | 老师张开让我了一夜av | 亚洲一区视频 | 大地资源二中文在线影视免费观看 | 欧美做受喷浆在线观看 | 一级片在线观看免费 | 2019中文在线观看 | 国产欧美视频一区 | 在线观看免费视频国产 | 精品九九九九九 | 成人亚洲黄色 | 亚洲伊人成人网 | 黄色xxxxx| 日韩激情| 老女人一毛片 | 中文字幕亚洲国产 | 图片区视频区小说区 | 97伊人超碰| 国产a大片 | 国产良妇出轨视频在线观看 | 福利在线免费视频 | 台湾佬久久 | 超碰在 | 嫩草影院久久 | 日韩欧美v | 日韩在线综合 | 天天看天天摸 | 嫩模一区 | 亚洲专区一 | 久久久人 | 欧美一级网站 | 国产卡一卡二 | 波多野结衣大片 | 成人免费直播 | 成人国产欧美 | 成人国产在线观看 | 免费的a级片 | 女女同性女同一区二区三区按摩 | 打屁股无遮挡网站 | 日本成片网 | 中文字幕观看在线 | 浮生影视在线观看免费 | 女厕厕露p撒尿八个少妇 | 日本在线视频不卡 | 国产91免费看 | wwwww在线观看 | 久久这里都是精品 | 成人毛片100部免费看 | 在线观看日本一区 | 亚洲视频免费在线播放 | 亚洲国产成人综合 | 国产美女操 | 欧美色图在线观看 | 中文字幕人妻一区二区在线视频 | 婷婷六月综合 | 天天射天天拍 | 麻豆传媒在线观看视频 | 日韩在线观看第一页 | 国产av无码专区亚洲a∨毛片 | 精品一区二区三区中文字幕 | 在线免费看污视频 | 成人毛片一级 | 日韩一级在线视频 | 老太太av | 九九精品网| 胖女人毛片 | 亚洲又粗又长 | 亚洲成人精品一区 | 香蕉视频最新网址 | 丰满熟妇人妻av无码区 | 精品人妻天天爽夜夜爽视频 | 性大毛片视频 | 韩国三级做爰视频 | 亚洲永久精品视频 | 51精品国产人成在线观看 | 69堂在线观看 | 女futa攻玩遍整个后宫 |