大話Java線程通信

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

啟動三個線程,一個生產(chǎn)者Thread-2,兩個消費者Thread-0和Thread-1

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


線程示意圖

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


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

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

消費者線程,那是個啥?

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

消費者線程

它在哪里,我怎么看不見。

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

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

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

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

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

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

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

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

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

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


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

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

那這兩個方法的作用是啥咧?

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

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

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

消費產(chǎn)品

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

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

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

JVM似乎很中意這個剛出生的娃娃。“看你天賦異稟,老哥賜你錦囊一幅,當(dāng)你迷茫的時候,記得拿出來看看”。

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

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

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

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

怎么用?

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

    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是喚醒等待相同對象monitor的其中一個線程Thread T(Thread T須調(diào)用了wait方法),至于是哪個線程被喚醒,這要取決于具體實現(xiàn),我們無法判斷。notifyAll是喚醒所有等待相同對象monitor的線程們。其他跟notify一樣。

Thread T不會立馬進入運行狀態(tài):

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

像這樣用就可以了:

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

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

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

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

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

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