Java 线程的 wait 和 notify 的神坑
也許我們只知道wait和notify是實(shí)現(xiàn)線程通信的,同時(shí)要使用synchronized包住,其實(shí)在開(kāi)發(fā)中知道這個(gè)是遠(yuǎn)遠(yuǎn)不夠的。接下來(lái)看看兩個(gè)常見(jiàn)的問(wèn)題。
問(wèn)題一:通知丟失
創(chuàng)建2個(gè)線程,一個(gè)線程負(fù)責(zé)計(jì)算,一個(gè)線程負(fù)責(zé)獲取計(jì)算結(jié)果。
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()?+?"等待計(jì)算結(jié)...");c.wait();}?catch?(InterruptedException?e)?{e.printStackTrace();}System.out.println(Thread.currentThread()?+?"計(jì)算結(jié)果為:"?+?c.total);}}public?static?void?main(String[]?args)?{Calculator?calculator?=?new?Calculator();//先啟動(dòng)獲取計(jì)算結(jié)果線程new?ReaderResult(calculator).start();calculator.start();} }我們會(huì)獲得預(yù)期的結(jié)果: Thread[Thread-1,5,main]等待計(jì)算結(jié)... Thread[Thread-1,5,main]計(jì)算結(jié)果為:5050但是我們修改為先啟動(dòng)計(jì)算線程呢? calculator.start(); new?ReaderResult(calculator).start();這是獲取結(jié)算結(jié)果線程一直等待: Thread[Thread-1,5,main]等待計(jì)算結(jié)...問(wèn)題分析
打印出線程堆棧:
"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上等待。發(fā)生這個(gè)現(xiàn)象就是常說(shuō)的通知丟失,在獲取通知前,通知提前到達(dá),我們先計(jì)算結(jié)果,計(jì)算完后再通知,但是這個(gè)時(shí)候獲取結(jié)果沒(méi)有在等待通知,等到獲取結(jié)果的線程想獲取結(jié)果時(shí),這個(gè)通知已經(jīng)通知過(guò)了,所以就發(fā)生丟失,那我們?cè)撊绾伪苊?可以設(shè)置變量表示是否被通知過(guò),修改代碼如下:
public?class?Calculator?extends?Thread?{int?total;boolean?isSignalled?=?false;@Overridepublic?void?run()?{synchronized?(this)?{isSignalled?=?true;//已經(jīng)通知過(guò)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)?{//判斷是否被通知過(guò)try?{System.out.println(Thread.currentThread()?+?"等待計(jì)算結(jié)...");c.wait();}?catch?(InterruptedException?e)?{e.printStackTrace();}System.out.println(Thread.currentThread()?+?"計(jì)算結(jié)果為:"?+?c.total);}}}public?static?void?main(String[]?args)?{Calculator?calculator?=?new?Calculator();new?ReaderResult(calculator).start();calculator.start();} }問(wèn)題二:假喚醒
兩個(gè)線程去刪除數(shù)組的元素,當(dāng)沒(méi)有元素的時(shí)候等待,另一個(gè)線程添加一個(gè)元素,添加完后通知?jiǎng)h除數(shù)據(jù)的線程。
public?class?EarlyNotify{private?List?list;public?EarlyNotify()?{list?=?Collections.synchronizedList(new?LinkedList());}public?String?removeItem()?throws?InterruptedException?{synchronized?(?list?)?{if?(?list.isEmpty()?)?{??//問(wèn)題在這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?{//啟動(dòng)第一個(gè)刪除元素的線程Thread?threadA1?=?new?Thread(runA,?"threadA1");threadA1.start();Thread.sleep(500);//啟動(dòng)第二個(gè)刪除元素的線程Thread?threadA2?=?new?Thread(runA,?"threadA2");threadA2.start();Thread.sleep(500);//啟動(dòng)增加元素的線程Thread?threadB?=?new?Thread(runB,?"threadB");threadB.start();Thread.sleep(1000);?//?wait?10?secondsthreadA1.interrupt();threadA2.interrupt();}?catch?(?InterruptedException?x?)?{}} }結(jié)果: threadA1:?threw?an?Exception!!! java.lang.IndexOutOfBoundsException:?Index:?0,?Size:?0這里發(fā)生了假喚醒,當(dāng)添加完一個(gè)元素然后喚醒兩個(gè)線程去刪除,這時(shí)只有一個(gè)元素,所以會(huì)拋出數(shù)組越界,這時(shí)我們需要在喚醒的時(shí)候再判斷一次是否還有元素。
修改代碼:
等待/通知的典型范式
從上面的問(wèn)題我們可歸納出等待/通知的典型范式。該范式分為兩部分,分別針對(duì)等待方(消費(fèi)者)和通知方(生產(chǎn)者)。
等待方遵循原則如下:
獲取對(duì)象的鎖
如果條件不滿(mǎn)足,那么調(diào)用對(duì)象的wait()方法,被通知后仍要檢查條件
條件滿(mǎn)足則執(zhí)行對(duì)應(yīng)的邏輯
對(duì)應(yīng)偽代碼如下:
synchronized(對(duì)象){while(條件不滿(mǎn)足){對(duì)象.wait();}對(duì)應(yīng)的處理邏輯 }通知方遵循原則如下:
獲得對(duì)象的鎖
改變條件
通知所有等待在對(duì)象上的線程
對(duì)應(yīng)偽代碼如下:
synchronized(對(duì)象){改變條件對(duì)象.notifyAll(); }總結(jié)
以上是生活随笔為你收集整理的Java 线程的 wait 和 notify 的神坑的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 记一次悲惨的 Excel 导出事件
- 下一篇: Java:未来已来