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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

Java 线程的 wait 和 notify 的神坑

發布時間:2025/3/21 java 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java 线程的 wait 和 notify 的神坑 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

也許我們只知道wait和notify是實現線程通信的,同時要使用synchronized包住,其實在開發中知道這個是遠遠不夠的。接下來看看兩個常見的問題。

問題一:通知丟失

創建2個線程,一個線程負責計算,一個線程負責獲取計算結果。

public?class?Calculator?extends?Thread?{int?total;@Overridepublic?void?run()?{synchronized?(this){for(int?i?=?0;?i?<?101;?i++){total?+=?i;}this.notify();}} }public?class?ReaderResult?extends?Thread?{Calculator?c;public?ReaderResult(Calculator?c)?{this.c?=?c;}@Overridepublic?void?run()?{synchronized?(c)?{try?{System.out.println(Thread.currentThread()?+?"等待計算結...");c.wait();}?catch?(InterruptedException?e)?{e.printStackTrace();}System.out.println(Thread.currentThread()?+?"計算結果為:"?+?c.total);}}public?static?void?main(String[]?args)?{Calculator?calculator?=?new?Calculator();//先啟動獲取計算結果線程new?ReaderResult(calculator).start();calculator.start();} }我們會獲得預期的結果: Thread[Thread-1,5,main]等待計算結... Thread[Thread-1,5,main]計算結果為:5050但是我們修改為先啟動計算線程呢? calculator.start(); new?ReaderResult(calculator).start();這是獲取結算結果線程一直等待: Thread[Thread-1,5,main]等待計算結...

問題分析

打印出線程堆棧:

"Thread-1"?prio=5?tid=0x00007f983b87e000?nid=0x4d03?in?Object.wait()?[0x0000000118988000]java.lang.Thread.State:?WAITING?(on?object?monitor)at?java.lang.Object.wait(Native?Method)-?waiting?on?<0x00000007d56fb4d0>?(a?com.concurrent.waitnotify.Calculator)at?java.lang.Object.wait(Object.java:503)at?com.concurrent.waitnotify.ReaderResult.run(ReaderResult.java:18)-?locked?<0x00000007d56fb4d0>?(a?com.concurrent.waitnotify.Calculator)

可以看出ReaderResult在Calculator上等待。發生這個現象就是常說的通知丟失,在獲取通知前,通知提前到達,我們先計算結果,計算完后再通知,但是這個時候獲取結果沒有在等待通知,等到獲取結果的線程想獲取結果時,這個通知已經通知過了,所以就發生丟失,那我們該如何避免?可以設置變量表示是否被通知過,修改代碼如下:

public?class?Calculator?extends?Thread?{int?total;boolean?isSignalled?=?false;@Overridepublic?void?run()?{synchronized?(this)?{isSignalled?=?true;//已經通知過for?(int?i?=?0;?i?<?101;?i++)?{total?+=?i;}this.notify();}} }public?class?ReaderResult?extends?Thread?{Calculator?c;public?ReaderResult(Calculator?c)?{this.c?=?c;}@Overridepublic?void?run()?{synchronized?(c)?{if?(!c.isSignalled)?{//判斷是否被通知過try?{System.out.println(Thread.currentThread()?+?"等待計算結...");c.wait();}?catch?(InterruptedException?e)?{e.printStackTrace();}System.out.println(Thread.currentThread()?+?"計算結果為:"?+?c.total);}}}public?static?void?main(String[]?args)?{Calculator?calculator?=?new?Calculator();new?ReaderResult(calculator).start();calculator.start();} }

問題二:假喚醒

兩個線程去刪除數組的元素,當沒有元素的時候等待,另一個線程添加一個元素,添加完后通知刪除數據的線程。

public?class?EarlyNotify{private?List?list;public?EarlyNotify()?{list?=?Collections.synchronizedList(new?LinkedList());}public?String?removeItem()?throws?InterruptedException?{synchronized?(?list?)?{if?(?list.isEmpty()?)?{??//問題在這list.wait();}//刪除元素String?item?=?(String)?list.remove(0);return?item;}}public?void?addItem(String?item)?{synchronized?(?list?)?{//添加元素list.add(item);//添加后,通知所有線程list.notifyAll();}}private?static?void?print(String?msg)?{String?name?=?Thread.currentThread().getName();System.out.println(name?+?":?"?+?msg);}public?static?void?main(String[]?args)?{final?EarlyNotify?en?=?new?EarlyNotify();Runnable?runA?=?new?Runnable()?{public?void?run()?{try?{String?item?=?en.removeItem();}?catch?(?InterruptedException?ix?)?{print("interrupted!");}?catch?(?Exception?x?)?{print("threw?an?Exception!!!\n"?+?x);}}};Runnable?runB?=?new?Runnable()?{public?void?run()?{en.addItem("Hello!");}};try?{//啟動第一個刪除元素的線程Thread?threadA1?=?new?Thread(runA,?"threadA1");threadA1.start();Thread.sleep(500);//啟動第二個刪除元素的線程Thread?threadA2?=?new?Thread(runA,?"threadA2");threadA2.start();Thread.sleep(500);//啟動增加元素的線程Thread?threadB?=?new?Thread(runB,?"threadB");threadB.start();Thread.sleep(1000);?//?wait?10?secondsthreadA1.interrupt();threadA2.interrupt();}?catch?(?InterruptedException?x?)?{}} }結果: threadA1:?threw?an?Exception!!! java.lang.IndexOutOfBoundsException:?Index:?0,?Size:?0

這里發生了假喚醒,當添加完一個元素然后喚醒兩個線程去刪除,這時只有一個元素,所以會拋出數組越界,這時我們需要在喚醒的時候再判斷一次是否還有元素。
修改代碼:

??public?String?removeItem()?throws?InterruptedException?{synchronized?(?list?)?{while?(?list.isEmpty()?)?{??//問題在這list.wait();}//刪除元素String?item?=?(String)?list.remove(0);return?item;}}

等待/通知的典型范式

從上面的問題我們可歸納出等待/通知的典型范式。該范式分為兩部分,分別針對等待方(消費者)和通知方(生產者)。

等待方遵循原則如下:

  • 獲取對象的鎖

  • 如果條件不滿足,那么調用對象的wait()方法,被通知后仍要檢查條件

  • 條件滿足則執行對應的邏輯

  • 對應偽代碼如下:

    synchronized(對象){while(條件不滿足){對象.wait();}對應的處理邏輯 }

    通知方遵循原則如下:

  • 獲得對象的鎖

  • 改變條件

  • 通知所有等待在對象上的線程

  • 對應偽代碼如下:

    synchronized(對象){改變條件對象.notifyAll(); }

    總結

    以上是生活随笔為你收集整理的Java 线程的 wait 和 notify 的神坑的全部內容,希望文章能夠幫你解決所遇到的問題。

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