摘要:如果有其它線程調(diào)用了相同對象的方法,那么處于該對象的等待池中的線程就會全部進入該對象的鎖池中,從新爭奪鎖的擁有權(quán)。
存在即合理wait,notify 和 notifyAll,這些在多線程中被經(jīng)常用到的保留關(guān)鍵字,在實際開發(fā)的時候很多時候卻并沒有被大家重視,而本文則是對這些關(guān)鍵字的使用進行描述。
在java中,每個對象都有兩個池,鎖池(monitor)和等待池(waitset),每個對象又都有wait、notify、notifyAll方法,使用它們可以實現(xiàn)線程之間的通信,只是平時用的較少。
wait(): 使當(dāng)前線程處于等待狀態(tài),直到另外的線程調(diào)用notify或notifyAll將它喚醒
notify(): 喚醒該對象監(jiān)聽的其中一個線程(規(guī)則取決于JVM廠商,F(xiàn)ILO,FIFO,隨機...)
notifyAll(): 喚醒該對象監(jiān)聽的所有線程
鎖池: 假設(shè)T1線程已經(jīng)擁有了某個對象(注意:不是類)的鎖,而其它的線程想要調(diào)用該對象的synchronized方法(或者synchronized塊),由于這些線程在進入對象的synchronized方法之前都需要先獲得該對象的鎖的擁有權(quán),但是該對象的鎖目前正被T1線程擁有,所以這些線程就進入了該對象的鎖池中。
等待池: 假設(shè)T1線程調(diào)用了某個對象的wait()方法,T1線程就會釋放該對象的鎖(因為wait()方法必須出現(xiàn)在synchronized中,這樣自然在執(zhí)行wait()方法之前T1線程就已經(jīng)擁有了該對象的鎖),同時T1線程進入到了該對象的等待池中。如果有其它線程調(diào)用了相同對象的notifyAll()方法,那么處于該對象的等待池中的線程就會全部進入該對象的鎖池中,從新爭奪鎖的擁有權(quán)。如果另外的一個線程調(diào)用了相同對象的notify()方法,那么僅僅有一個處于該對象的等待池中的線程(隨機)會進入該對象的鎖池.
注意事項在調(diào)用wait(), notify()或notifyAll()的時候,都必須獲得某個對象(注意:不是類)的鎖。
永遠在循環(huán)(loop)里調(diào)用 wait 和 notify,而不是在 If 語句中
永遠在synchronized的函數(shù)或?qū)ο罄锸褂?b>wait、notify和notifyAll,不然Java虛擬機會生成 IllegalMonitorStateException。
使用案例 - 生產(chǎn)消費private static int i = 0; static void product() {//生產(chǎn)者 System.out.println("P->" + (++i)); } static void consumer() {//消費者 System.out.println("C->" + i); } public static void main(String[] args) { new Thread(() -> { while (true) { product(); } }).start(); new Thread(() -> { while (true) { consumer(); } }).start(); } ////////////////////////日志//////////////////////// //P->1 //P->2 //P->3 //P->4 //C->1 ////////////////////////日志////////////////////////
分析: 從日志中可以看到數(shù)據(jù)會出現(xiàn)多次生產(chǎn)或多次消費的問題,因為在線程執(zhí)行過程中,兩個線程缺少協(xié)作關(guān)系,都是各干各的,T1線程只管生產(chǎn)數(shù)據(jù),不管T2線程是否處理了。
改進方案 - 變量消息傳遞private static int i = 0; private static boolean isProduction = true; static void product() {//生產(chǎn)者 if (isProduction) { System.out.println("P->" + (++i)); isProduction = false; } } static void consumer() {//消費者 if (!isProduction) { System.out.println("C->" + i); isProduction = true; } }
分析: 這種情況下我們通過維護一個變量的方式,通知對方,但是效率及其差,線程頻繁請求與判斷大大的浪費了系統(tǒng)資源,雖然滿足了當(dāng)前要求,但并非是可選方案...
改進方案 - wait/notify上文已經(jīng)介紹了使用wait和notify的前提了,接下來看案例
private final static byte[] LOCK = new byte[0];//定義一個鎖對象 private static boolean isProduction = true;//消息投遞 private static int i = 0;//生產(chǎn)的消息 static void product() { synchronized (LOCK) {// 必須是在 synchronized中 使用 wait/notify/notifyAll try { if (isProduction) {//如果標(biāo)示位為生產(chǎn)狀態(tài),則繼續(xù)生產(chǎn) System.out.println("P->" + (++i)); isProduction = false; LOCK.notify();//消費者可以消費了 } else { LOCK.wait();//說明生產(chǎn)出來的數(shù)據(jù)還未被消費掉 } } catch (InterruptedException e) { e.printStackTrace(); } } } static void consumer() { try { synchronized (LOCK) { if (isProduction) {//如果當(dāng)前還在生產(chǎn),那么就暫停消費者線程 LOCK.wait(); } else { System.out.println("C->" + i); isProduction = true; LOCK.notify();//通知我已經(jīng)消費完畢了 } } } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { new Thread(() -> { while (true) { product(); } }).start(); new Thread(() -> { while (true) { consumer(); } }).start(); } ////////////////////////日志//////////////////////// //P->1 //C->1 //P->2 //C->2 //..... //P->354217 //C->354217 ////////////////////////日志////////////////////////
分析: 一切都是那么完美,在T1線程中,調(diào)用LOCK.wait()將當(dāng)前線程移入等待池中,并交出執(zhí)行權(quán),鎖池中的其他線程去競爭并取得鎖的使用權(quán)(T2線程獲取),當(dāng)T2線程消費完畢后,調(diào)用LOCK.notify()方法通知當(dāng)前對象鎖等待池中的其中一個線程(因為這里notify是基于JVM算法而定,因為我們只有兩個線程,所以T1線程會接收到T2線程發(fā)出的通知,從而繼續(xù)生產(chǎn)數(shù)據(jù)。
問題: 雖然一對一沒有問題,但假設(shè)多個生產(chǎn)者多個消費者的情況下怎么辦呢?
BUG - 埋點public static void main(String[] args) { Stream.of("P1", "P2", "P3", "P4").forEach(name -> new Thread(() -> { while (true) { product(); } }, name).start()); Stream.of("C1", "C2").forEach(name -> new Thread(() -> { while (true) { consumer(); } }, name).start()); } ////////////////////////日志//////////////////////// //P2 -> 1 //C2 -> 1 //P2 -> 2 //C1 -> 2 //P3 -> 3 ////////////////////////日志////////////////////////
分析: 居然不執(zhí)行了,借助前面說過的 死鎖分析知識,我們看看是不是發(fā)生死鎖了
結(jié)果表明,雖然沒有Found one deadlock...字眼,但是可以看到有個線程都被wait住了,沒有被釋放,所以導(dǎo)致我們當(dāng)前無法繼續(xù)生產(chǎn)消費
解決方案 - notifyAllLOCK.notifyAll();//通知所有線程,我已經(jīng)消費完畢了,你們繼續(xù)生產(chǎn)
分析: 這里只修改了一句代碼,就是將consumer方法中的notify -> notifyAll,由通知單個線程變成通知所有在等待池中的線程
P1 -> 1 C1 -> 1 P2 -> 2 C2 -> 2 ... P3 -> 42894 C1 -> 42894 ... P1 -> 42898 C1 -> 42898- 說點什么
全文代碼:https://gitee.com/battcn/battcn-concurent/tree/master/Chapter1-1/battcn-thread/src/main/java/com/battcn/chapter7
個人QQ:1837307557
battcn開源群(適合新手):391619659
微信公眾號:battcn(歡迎調(diào)戲)
喜大普奔,迎來了十一國慶節(jié)....
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/67636.html
摘要:文本將介紹兩種可以優(yōu)雅的終止線程的方式第一種在多線程模式中有一種叫兩步終止的模式可以優(yōu)雅的終止線程,這種模式采用了兩個步驟來終止線程,所以叫兩步終止模式。 Java中原來在Thread中提供了stop()方法來終止線程,但這個方法是不安全的,所以一般不建議使用。文本將介紹兩種可以優(yōu)雅的終止線程的方式... 第一種 在JAVA《Java多線程模式》中有一種叫Two-Phase Term...
摘要:造成當(dāng)前線程在接到信號被中斷或到達指定最后期限之前一直處于等待狀態(tài)。該線程從等待方法返回前必須獲得與相關(guān)的鎖。如果線程已經(jīng)獲取了鎖,則將喚醒條件隊列的首節(jié)點。 一、寫在前面 在前幾篇我們聊了 AQS、CLH、ReentrantLock、ReentrantReadWriteLock等的原理以及其源碼解讀,具體參見專欄 《非學(xué)無以廣才》 這章我們一起聊聊顯示的Condition 對象。 ...
摘要:在前面的文章中介紹過觀察者模式及并發(fā)編程的基礎(chǔ)知識,為了讓大家更好的了解觀察者模式故而特意寫了這篇番外概述在多線程下我們需要知道當(dāng)前執(zhí)行線程的狀態(tài)是什么比如運行,關(guān)閉,異常等狀態(tài)的通知,而且不僅僅是更新當(dāng)前頁面。 在前面的文章中介紹過 觀察者模式 及 并發(fā)編程的基礎(chǔ)知識,為了讓大家更好的了解觀察者模式故而特意寫了這篇番外.. 概述 在Java多線程下,我們需要知道當(dāng)前執(zhí)行線程的狀態(tài)是...
摘要:那并發(fā)里面的理論和模型是什么呢那便要從操作系統(tǒng)中解決并發(fā)問題的一種模型管程講起了。當(dāng)一個進程使用完管程后,它必須釋放管程并喚醒等待管程的某一個進程??偨Y(jié)在并發(fā)編程領(lǐng)域,有兩大核心問題互斥和同步,而這兩個問題,管程模型都可以解決。 為什么需要了解管程 Java并發(fā)編程是Java中高級程序員必備的一項技能,但是真正學(xué)明白并發(fā)編程也并非易事。正如Java并發(fā)編程實踐中的一句話編寫正確的程序并...
摘要:一般差異簡單來說,是一個用于線程同步的實例方法。暫停當(dāng)前線程,不釋放任何鎖。用來線程間通信,使擁有該對象鎖的線程等待直到指定時間或。執(zhí)行對該對象加的同步代碼塊。 在JAVA的學(xué)習(xí)中,不少人會把sleep和wait搞混,認(rèn)為都是做線程的等待,下面主要介紹下這倆者是什么,及了解它們之間的差異和相似之處。 一般差異 簡單來說,wait()是一個用于線程同步的實例方法。因為定義在java.l...
閱讀 3966·2021-10-08 10:05
閱讀 3036·2021-09-27 13:57
閱讀 2748·2019-08-29 11:32
閱讀 1074·2019-08-28 18:18
閱讀 1369·2019-08-28 18:05
閱讀 2044·2019-08-26 13:39
閱讀 932·2019-08-26 11:37
閱讀 2134·2019-08-26 10:37