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