大話Java線程通信

“怎么還慢吞吞的!”,JVM翹著二郎腿在調(diào)度室里大聲喊道?!癟hread-2,你已經(jīng)被我創(chuàng)建出來(lái)了,趕緊干活!”。

啟動(dòng)三個(gè)線程,一個(gè)生產(chǎn)者Thread-2,兩個(gè)消費(fèi)者Thread-0和Thread-1

Thread-2這才反應(yīng)過(guò)來(lái),看看自己的身體,是個(gè)結(jié)構(gòu)分明、線條優(yōu)美的線程棧。先看看自己有啥東西:一個(gè)程序計(jì)數(shù)器、一連串的函數(shù)棧幀。程序計(jì)數(shù)器記錄程序執(zhí)行到哪里了,函數(shù)棧幀是一個(gè)方法的信息體,里面包含局部變量表、操作數(shù)棧等。


線程示意圖

“好的,老板。我看到我有一個(gè)run方法,里面調(diào)用了produce()方法,用人類的話說(shuō),我應(yīng)該是個(gè)生產(chǎn)者啊”。JVM點(diǎn)了點(diǎn)頭,這小子挺機(jī)靈的。“那你趕緊去生產(chǎn)產(chǎn)品吧,消費(fèi)者線程打電話催了好幾次了”。


我是個(gè)生產(chǎn)者線程

生產(chǎn)者線程的run方法

消費(fèi)者線程,那是個(gè)啥?

跟你一樣,也是由我創(chuàng)建出來(lái)的一個(gè)線程。只是職責(zé)與你不同,你負(fù)責(zé)生產(chǎn)產(chǎn)品,它負(fù)責(zé)消費(fèi)產(chǎn)品。

消費(fèi)者線程

它在哪里,我怎么看不見(jiàn)。

你們彼此都是有獨(dú)立空間的,但是你們需要協(xié)作完成工作,你得注意兩點(diǎn),①操作臨界區(qū)數(shù)據(jù)時(shí)要進(jìn)行互斥(同一時(shí)刻只能有一個(gè)線程操作這個(gè)共享變量),②修改后你得通知其他線程可以操作這個(gè)共享變量了;

你叫Producer類,你有一個(gè)放在方法區(qū)的實(shí)例變量taskQueue(List<Integer> taskQueue),你往這個(gè)容器中放東西,消費(fèi)者線程的Consumer類也要持有同一個(gè)實(shí)例變量,這樣它們就可以從這個(gè)容器里取產(chǎn)品了。JVM怕它不懂,多解釋了幾句。

看老大停頓了,天性活潑多問(wèn)的Thread-2正準(zhǔn)備開(kāi)口接著問(wèn)...

“先調(diào)用produce()函數(shù)棧幀,走一遍指令再說(shuō)”。JVM沒(méi)搭理它,發(fā)出了指令。

生產(chǎn)產(chǎn)品

過(guò)了1ms,活潑的Thread-2又打電話來(lái)了。我已經(jīng)走了一遍了,剛開(kāi)始讓我判斷是否生產(chǎn)到5個(gè)了,如果到了就調(diào)用wait()方法。因?yàn)閿?shù)量為0所以就繼續(xù)走下面的邏輯,生產(chǎn)了一個(gè)產(chǎn)品放在taskQueue中,然后調(diào)用了notify()方法。調(diào)用這兩個(gè)方法的時(shí)候,到底發(fā)生了什么???

你這大大咧咧的性格,怎么忘了描述最重要的那點(diǎn)了呢?

Thread-2摸摸自己紅撲撲的臉蛋,生怕老板罵它。

你剛開(kāi)始運(yùn)行的時(shí)候,系統(tǒng)是不是讓你去搶占taskQueue的monitor,你首先得搶到這個(gè)實(shí)例變量的monitor,你才能運(yùn)行。才有后面調(diào)用wait()或notify()的事。
這些都是synchronized在起作用,它的作用是在代碼塊前后加上monitorenter和monitorexit兩個(gè)字節(jié)碼指令,這樣就不會(huì)有其他線程同時(shí)操作taskQueue這個(gè)變量。這個(gè)就是我們常說(shuō)的互斥鎖。當(dāng)然synchronized可以加在方法上或者包裹一個(gè)代碼塊,那說(shuō)來(lái)就話長(zhǎng)了。

我想起來(lái)了,我在taskQueue的對(duì)象頭中確實(shí)看到了是我持有了鎖。執(zhí)行taskQueue.wait()或taskQueue.notify()方法的時(shí)候,是需要持有這個(gè)對(duì)象的monitor的。


對(duì)象的內(nèi)存布局示意圖

是的,其中wait()、notify()這樣的方法是要在synchronized關(guān)鍵字包裹的中才能執(zhí)行的,否則會(huì)拋出IllegalMonitorStateException異常。

那這兩個(gè)方法的作用是啥咧?

你先生產(chǎn)到5個(gè)產(chǎn)品。

JVM剛準(zhǔn)備看會(huì)新聞,Thread-2來(lái)電了。我生產(chǎn)5個(gè)了,調(diào)用了wait()方法,釋放了monitor。我現(xiàn)在身子就好像僵硬了一樣,什么都干不了。JVM哈哈哈一笑,你這是把自己放在了一個(gè)等待池中,等待taskQueue實(shí)例變量的鎖。讓我跟你說(shuō)說(shuō)現(xiàn)在外面都發(fā)生了什么吧。

還記得你生產(chǎn)第一個(gè)產(chǎn)品的時(shí)候,就調(diào)用了notify()方法吧。這個(gè)方法會(huì)通知其他的線程來(lái)消費(fèi)產(chǎn)品。但是他們沒(méi)有進(jìn)來(lái)消費(fèi),因?yàn)槟氵€沒(méi)有釋放taskQueue的monitor,等到你生產(chǎn)滿5個(gè)后,會(huì)調(diào)用wait()方法,這個(gè)方法會(huì)讓你釋放掉monitor,并進(jìn)入等待池。
消費(fèi)者線程那邊也是同樣的邏輯,他們會(huì)在收到通知后去競(jìng)爭(zhēng)taskQueue的monitor,競(jìng)爭(zhēng)到的線程開(kāi)始進(jìn)入consume()方法,它同樣需要加互斥鎖。當(dāng)他們消費(fèi)完了之后,會(huì)調(diào)用wait()方法,你就有機(jī)會(huì)去爭(zhēng)奪taskQueue的monitor。搶到以后,你就可以繼續(xù)工作了。

消費(fèi)產(chǎn)品

最終的結(jié)果你可以看下:

執(zhí)行結(jié)果

Thread-0先運(yùn)行,發(fā)現(xiàn)集合為空,則進(jìn)入等待池。生產(chǎn)者生產(chǎn)產(chǎn)品,達(dá)到5個(gè)后進(jìn)入wait狀態(tài),釋放taskQueue的monitor,消費(fèi)者線程Thread-0獲得taskQueue的monitor進(jìn)入運(yùn)行狀態(tài)。以此類推消費(fèi)線程Thread-1的行為。

JVM似乎很中意這個(gè)剛出生的娃娃?!翱茨闾熨x異稟,老哥賜你錦囊一幅,當(dāng)你迷茫的時(shí)候,記得拿出來(lái)看看”。

錦囊第一法:搬完磚了怎么休息?

調(diào)用wait方法,它是Object類的方法,final native修飾,即本地方法,不可被子類重寫(xiě)!必須在sychronized塊中調(diào)用;
作用:當(dāng)前線程Thread T釋放對(duì)象鎖,并將自己放在等待池(wait set)中。直到其他線程獲取這個(gè)對(duì)象的monitor控制權(quán),以及發(fā)生以下四種情況之一時(shí),線程Thread T將被喚醒:
①其他線程持有同一個(gè)對(duì)象的monitor并調(diào)用notify()方法
②其他線程持有同一個(gè)對(duì)象的monitor并調(diào)用notifyAll()方法
③超過(guò)等待時(shí)間
④被其他線程打斷

調(diào)用wait方法的要點(diǎn):

  • 當(dāng)前線程必須擁有這個(gè)對(duì)象的monitor,否則會(huì)拋出IllegalMonitorStateException異常;
  • 當(dāng)前線程將自己放在wait set中,并解除所有在這個(gè)對(duì)象上的同步聲明;
  • 當(dāng)前線程調(diào)用wait方法后返回,線程及對(duì)象的同步狀態(tài)與調(diào)用wait方法時(shí)是一致的;
  • 當(dāng)被喚醒時(shí),會(huì)與其他線程一起競(jìng)爭(zhēng)獲取對(duì)象的同步權(quán)(獲取monitor);

怎么用?

線程的喚醒緣故不一定是上面提到的四種情況,有時(shí)候可能會(huì)是假喚醒狀態(tài),所以需要在輪詢+條件判斷的代碼塊中使用,在不滿足條件時(shí),讓線程一直等待:

    synchronized (obj) {
        while (condition does not hold)
        obj.wait(timeout);
         ... // Perform action appropriate to condition
     }

錦囊第二法:怎么通知其他線程小伙伴?

調(diào)用notify()或者notifyAll()方法。它們都是Object類的final native方法,必須在sychronized塊中調(diào)用;
作用:notify是喚醒等待相同對(duì)象monitor的其中一個(gè)線程Thread T(Thread T須調(diào)用了wait方法),至于是哪個(gè)線程被喚醒,這要取決于具體實(shí)現(xiàn),我們無(wú)法判斷。notifyAll是喚醒所有等待相同對(duì)象monitor的線程們。其他跟notify一樣。

Thread T不會(huì)立馬進(jìn)入運(yùn)行狀態(tài):

①當(dāng)前調(diào)用了notify()方法后,當(dāng)前線程不一定會(huì)立馬釋放對(duì)象的monitor,直到當(dāng)前線程的同步塊執(zhí)行完成后才會(huì)釋放;
②Thread T得到可以喚醒的通知,對(duì)象的monitor也被釋放了,此時(shí)會(huì)與其他等待當(dāng)前對(duì)象monitor的對(duì)象一同競(jìng)爭(zhēng)這個(gè)monitor,獲取到這個(gè)對(duì)象的monitor后方可進(jìn)入運(yùn)行狀態(tài)。

像這樣用就可以了:

   synchronized(lockObject){
      //establish_the_condition;
      lockObject.notify();
      //any additional code if needed
}

Thread-2就這樣慢慢熟悉了如何與其他線程進(jìn)行協(xié)作,保證數(shù)據(jù)安全地運(yùn)行,后來(lái)它了解到人類為了寫(xiě)出線程安全的代碼,需要考慮到原子性、可見(jiàn)性、有序性等??磥?lái)又有新東西可以學(xué)了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 本文主要講了java中多線程的使用方法、線程同步、線程數(shù)據(jù)傳遞、線程狀態(tài)及相應(yīng)的一些線程函數(shù)用法、概述等。 首先講...
    李欣陽(yáng)閱讀 2,602評(píng)論 1 15
  • Java多線程學(xué)習(xí) [-] 一擴(kuò)展javalangThread類 二實(shí)現(xiàn)javalangRunnable接口 三T...
    影馳閱讀 3,115評(píng)論 1 18
  • 本文出自 Eddy Wiki ,轉(zhuǎn)載請(qǐng)注明出處:http://eddy.wiki/interview-java.h...
    eddy_wiki閱讀 2,303評(píng)論 0 14
  • 人生的大多數(shù)遇見(jiàn) 都會(huì)不可避免的分離 愿你在我看不見(jiàn)的地方 過(guò)得比從前更好
    冰玄海棠閱讀 110評(píng)論 0 1
  • Lamp環(huán)境下配置域名 本地安裝的虛擬機(jī)我們可以通過(guò)ifconfig命令來(lái)查看ip,通過(guò)ip在瀏覽器中訪問(wèn)我們頁(yè)面...
    曹淵說(shuō)創(chuàng)業(yè)閱讀 1,389評(píng)論 0 0

友情鏈接更多精彩內(nèi)容