解析JVM线程同步机制
http://blog.csdn.net/thl789/article/details/566494
對多線程的支持一般是在OS級的,而Java將其做在了語言級別,這其中最吸引人的莫過于Java對線程同步(互斥與協(xié)作)的支持。本文分析了JVM(Java Virtual Machine)內(nèi)部實現(xiàn)的監(jiān)視器同步機(jī)制,并結(jié)合經(jīng)典的生產(chǎn)者消費(fèi)者同步問題,闡述Java語言級別上對此機(jī)制的支持。
關(guān)鍵詞:同步,互斥,協(xié)作,監(jiān)視器,鎖
Keyword: Synchronization, Mutual Exclusion (Mutex), Cooperation, Monitor, Lock
?
目 ?錄
摘要... 1
目? 錄... 1
知識準(zhǔn)備... 2
一、線程同步:互斥和協(xié)作... 2
二、解決同步的方案... 2
三、線程同步模型——監(jiān)視器(Monitor)機(jī)制... 3
? 3.1 監(jiān)視器模型... 3
? 3.2 監(jiān)視器實現(xiàn)互斥... 4
? 3.3 監(jiān)視器實現(xiàn)協(xié)作... 5
四、JVM線程同步的實現(xiàn)... 6
? 4.1 JVM線程模型... 6
? 4.2 對象鎖... 6
? 4.3 Java語言對線程同步的支持... 7
??? 4.3.1 同步方法與同步語句... 7
??? 4.3.2 協(xié)作——wait & notify/notifyAll7
五、利用JVM線程同步解決生產(chǎn)者消費(fèi)者問題... 8
? 5.1 生產(chǎn)者消費(fèi)者問題相關(guān)類層次... 8
? 5.2 生產(chǎn)者線程——threadSynch.ProducerConsumer.Producer9
? 5.3 消費(fèi)者線程——threadSynch.ProducerConsumer.Consumer10
? 5.4 主程序——threadSynch.ProducerConsumer. ProducerConsumer11
? 5.5 程序運(yùn)行時的一個快照... 11
六、總結(jié)... 12
參考資料及進(jìn)一步閱讀... 13
關(guān)于作者... 13
?
知識準(zhǔn)備
閱讀本文前,你應(yīng)該具有操作系統(tǒng)的基本知識,知道Java的基本運(yùn)行模式,最好還有過多線程的編程經(jīng)驗。這些知識準(zhǔn)備,可以閱讀本文后面所附的參考資料。
一、線程同步:互斥和協(xié)作
早期,順序程序設(shè)計(Sequential Programming)的模式一般是串行的執(zhí)行“輸入——處理——輸出”,執(zhí)行過程中可能還有用戶的交互或者執(zhí)行其它I/O操作,而這一切直到最后的輸出,系統(tǒng)最珍貴的CPU資源都由這個程序?qū)?yīng)的進(jìn)程(Process)占有。為了提高CPU的利用率和任務(wù)的并行化,引入了并發(fā)程序設(shè)計(Concurrent Programming),也就相應(yīng)的在OS內(nèi)部有了并發(fā)進(jìn)程的概念。在支持線程(Thread)的系統(tǒng)中,進(jìn)程的實現(xiàn)和思考方法也適用,只是在OS調(diào)度的最小單元和資源的分配單元上有所區(qū)別。但是也要看對線程的支持實現(xiàn)在哪一級上,所以也就有了三種線程實現(xiàn)方式:內(nèi)核級線程、用戶級線程和混和式線程。(關(guān)于這方面的知識,可閱讀參考文獻(xiàn)中的1或2)
支持多線程的系統(tǒng)中,并發(fā)線程在運(yùn)行過程時,會有同步(Synchronization)的需求,同步包括了互斥(Mutual Exclusion,簡記為Mutex)與協(xié)作(Cooperation)兩個方面。多個線程交叉訪問臨界資源的時候,如果不把臨界區(qū)進(jìn)行互斥管理,執(zhí)行的結(jié)果可能就不是你預(yù)想的(這種沒有對臨界資源進(jìn)行互斥訪問而出現(xiàn)的情況,你幾乎可以在任何講述并發(fā)編程的書中找到例子,這里不再贅述),這就需要將多個線程對臨界區(qū)的執(zhí)行串行化。兩個或多個線程運(yùn)行中,可能要協(xié)作完成一整件事,但是它們是相對獨(dú)立運(yùn)行的,相互之間并不影響,所以需要某種機(jī)制使某個線程知道它下一步執(zhí)行所依賴的條件是不是滿足,而這個條件是否滿足是要看另外一個線程的執(zhí)行情況的,也就是通過某種協(xié)作機(jī)制使線程在單獨(dú)執(zhí)行并行化的基礎(chǔ)上,實現(xiàn)多個線程的協(xié)作串行執(zhí)行。
二、解決同步的方案
解決線程同步問題,早期的科學(xué)家提出了一些對于臨界區(qū)管理的方法,后來在操作系統(tǒng)中實現(xiàn)了信號量機(jī)制來解決經(jīng)典的同步問題,在IEEE的POSIX(Portable Operating Systems Interface)標(biāo)準(zhǔn)中就進(jìn)一步把線程進(jìn)行了標(biāo)準(zhǔn)化,簡稱pThread標(biāo)準(zhǔn),這其中也標(biāo)準(zhǔn)化了線程同步機(jī)制——互斥量等,目前很多類UNIX(UNIX-like)操作系統(tǒng)都已經(jīng)實現(xiàn)了pThread。
實現(xiàn)臨界區(qū)的管理可以采用軟件方法,也可以用硬件方法。T.Dekker和G.L.Perterson分別提出了Dekker算法和Perterson算法,用軟件方法實現(xiàn)了對臨界區(qū)管理。硬件方法可以采用關(guān)中斷來解決,但是這種方式使系統(tǒng)效率大大的降低,甚至如果關(guān)中斷處理不當(dāng),還會使系統(tǒng)無法正常調(diào)度,而且也不適用于多處理器情況?,F(xiàn)在幾乎所有的操作系統(tǒng)都實現(xiàn)了信號量(Semaphore)機(jī)制。信號量(Semaphore)通過一個記錄當(dāng)前使用情況的標(biāo)記value,等待該信號量的線程隊列queue和相應(yīng)的PV操作原語實現(xiàn)同步。P操作相當(dāng)于線程要使用該信號量所標(biāo)志的資源,通過P操作來獲得,如果當(dāng)前不可用,該線程就要被掛起;V操作相當(dāng)于線程使用完該信號量所標(biāo)志的資源,通過該操作來釋放它,如果有線程在等待這個資源并且資源當(dāng)前可用,就采取某種策略選擇一個等待的線程,讓它擁有該資源,并繼續(xù)執(zhí)行。根據(jù)value的初始值,信號量可以用來實現(xiàn)線程的互斥與協(xié)作。POSIX pThread的互斥量(Mutex)其實是信號量的一種特殊形式,但是使用互斥量要比用通用的信號量的同步機(jī)制來得容易。
Java利用JVM對線程執(zhí)行期的完全控制,實現(xiàn)了監(jiān)視器(Monitor)機(jī)制的線程同步,下面章節(jié)先介紹監(jiān)視器機(jī)制,然后分析監(jiān)視器機(jī)制在JVM中的實現(xiàn)。
三、線程同步模型——監(jiān)視器(Monitor)機(jī)制
監(jiān)視器支持上文所述的兩種線程同步:互斥與協(xié)作,而JVM通過對象鎖實現(xiàn)了監(jiān)視器機(jī)制。本節(jié)就來闡述監(jiān)視器模型,并分析它如何實現(xiàn)線程的互斥與協(xié)作。
3.1 監(jiān)視器模型
?
?
圖一、監(jiān)視器模型
?
圖一是監(jiān)視器模型,監(jiān)視器包括了三個部分,入口區(qū)、擁有區(qū)和等待區(qū),入口區(qū)和等待區(qū)內(nèi)可能有多個線程,但是任何時刻最多只有一個線程擁有該監(jiān)視器。
線程對監(jiān)視器的操作原語如下:
-“進(jìn)入”監(jiān)視器指線程進(jìn)入入口區(qū),準(zhǔn)備獲取監(jiān)視器,此時如果沒有別的線程擁有該監(jiān)視器,則這個線程擁有此監(jiān)視器,否則它要在入口區(qū)等待;
-“獲取”監(jiān)視器指在入口區(qū)和等待區(qū)的線程按照某種策略機(jī)制被選擇可擁有該監(jiān)視器時的操作;
-“擁有”監(jiān)視器的線程在它擁有該監(jiān)視器的時候排他地占有它,從而阻止其它線程的進(jìn)入;
-“釋放”監(jiān)視器 擁有監(jiān)視器的線程執(zhí)行完監(jiān)視器范圍內(nèi)的代碼或異常退出之后,要釋放掉它所擁有的此監(jiān)視器。
監(jiān)視器實現(xiàn)的是對臨界區(qū)的管理,對臨界區(qū)調(diào)度原則有16字要求——無空等待,有空讓進(jìn),擇一而入,算法可行。展開來說就是:
- 一次至多一個線程能夠在臨界區(qū)內(nèi);
- 不能讓一個線程無限地留在臨界區(qū);
- 不能強(qiáng)迫一個線程無限地等待進(jìn)入臨界區(qū);
- 不能因所選的調(diào)度策略而造成線程的饑餓(Starving),甚至死鎖(Dead Lock)。
下面以經(jīng)典的“生產(chǎn)者-消費(fèi)者問題”為例,來分析利用監(jiān)視器如何實現(xiàn)生產(chǎn)者進(jìn)程與消費(fèi)者進(jìn)程之間的互斥與協(xié)作。簡化后的該問題的描述如下:生產(chǎn)者進(jìn)程和消費(fèi)者進(jìn)程都對同一個緩沖區(qū)操作,生產(chǎn)者生產(chǎn)產(chǎn)品放到緩沖區(qū),消費(fèi)者消費(fèi)緩沖區(qū)內(nèi)的產(chǎn)品;如果緩沖區(qū)非空,則消費(fèi)者讀取緩沖區(qū)的產(chǎn)品,消費(fèi)掉產(chǎn)品的同時將緩沖區(qū)清空,否則消費(fèi)者等待;如果緩沖區(qū)為空,則生產(chǎn)者生產(chǎn)產(chǎn)品放到緩沖區(qū),否則生產(chǎn)者等待。
3.2 監(jiān)視器實現(xiàn)互斥
利用監(jiān)視器實現(xiàn)進(jìn)程之間的互斥理解起來非常簡單。生產(chǎn)者消費(fèi)者問題中的緩沖區(qū)是臨界資源,需要互斥訪問,可以用一個監(jiān)視器來保護(hù)。生產(chǎn)者或消費(fèi)者要訪問該共享的緩沖區(qū)首先必須擁有這個監(jiān)視器,在監(jiān)視器被別的線程占有時,該訪問線程必須在入口區(qū)或等待區(qū)等待。生產(chǎn)者和消費(fèi)者對緩沖區(qū)的互斥訪問關(guān)系如圖二所示。
?
?
圖二、生產(chǎn)者-消費(fèi)者之間的互斥
?
生產(chǎn)者與消費(fèi)者各自獨(dú)立地執(zhí)行,只有當(dāng)它們需要訪問緩沖區(qū)(存放生產(chǎn)的產(chǎn)品或消費(fèi)產(chǎn)品)的時候,才需要獲得與該緩沖區(qū)關(guān)聯(lián)的監(jiān)視器,如果當(dāng)前該監(jiān)視器不能獲得,它們就在監(jiān)視器的入口區(qū)等待。它們在離開臨界區(qū)的時候,釋放監(jiān)視器,以允許其它競爭該監(jiān)視器的線程進(jìn)入。如果一個線程已經(jīng)獲得了監(jiān)視器,但是緩沖區(qū)的內(nèi)容卻還不滿足自己的需要,它就必須等待并釋放掉監(jiān)視器,從而允許其它線程進(jìn)入,關(guān)于線程協(xié)作的問題參考下面的小節(jié)。
3.3 監(jiān)視器實現(xiàn)協(xié)作
?????? 上面小節(jié)主要說明了生產(chǎn)者和消費(fèi)者對緩沖區(qū)的互斥訪問關(guān)系,但是沒有詳細(xì)談到“wait & Release the Monitor”這個活動,這個主要牽涉了監(jiān)視器實現(xiàn)的線程間的協(xié)作關(guān)系。
生產(chǎn)者和消費(fèi)者通過監(jiān)視器協(xié)作完成生產(chǎn)者消費(fèi)者問題的活動圖如圖三所示。
?
?
圖三、生產(chǎn)者-消費(fèi)者之間的協(xié)作
?
重點關(guān)注圖中的綠色部分,擁有監(jiān)視器的線程檢測到當(dāng)前緩沖區(qū)不符合自己要求的情況下“wait & Release the Monitor”,這樣這個線程就釋放掉了該監(jiān)視器,并且進(jìn)入到等待區(qū)。假設(shè)這個線程是生產(chǎn)者Thread_p,此時無論消費(fèi)者線程Thread_c處于入口區(qū)還是等待區(qū),Thread_c都可能獲得該監(jiān)視器并繼續(xù)執(zhí)行。Thread_c消費(fèi)完產(chǎn)品并設(shè)置好緩沖區(qū)之后,它通知(notify/notifyAll)等待區(qū)的線程,正在等待(wait)的線程要求的條件滿足后可以競爭獲取監(jiān)視器,并繼續(xù)執(zhí)行。
注意,等待線程并不是收到notify消息立即就能獲取監(jiān)視器,還要等發(fā)送notify消息的線程離開臨界區(qū)(此時,已經(jīng)釋放監(jiān)視器)時它才能競爭獲取。這里的“競爭獲取”指的是處于入口區(qū)和等待區(qū)的線程按照某種調(diào)度策略被選擇進(jìn)入監(jiān)視器,這種策略可以是用先入先出(FIFO)隊列實現(xiàn)的先來先服務(wù)管理,但采用哪種實現(xiàn)要看虛擬機(jī)的具體實現(xiàn)。因為這種等待線程的競爭獲取監(jiān)視器現(xiàn)象的存在,等待線程在擁有監(jiān)視器之后要判斷當(dāng)前條件是否真的滿足需要——狀態(tài)有可能被先于它獲得監(jiān)視器的其它線程改變。
四、JVM線程同步的實現(xiàn)
JVM通過對象鎖實現(xiàn)監(jiān)視器的模型的線程同步機(jī)制。其實現(xiàn)是通過在JVM內(nèi)部為每個對象和類都關(guān)聯(lián)一個鎖;語言層次上用同步方法或同步語句標(biāo)識臨界區(qū),每個對象都實現(xiàn)等待/通知方法等方式來通過實現(xiàn)線程同步的。
4.1 JVM線程模型
Java中的線程是在用戶級實現(xiàn)的,即,在操作系統(tǒng)看來JVM是一個進(jìn)程,而Java線程是JVM內(nèi)部實現(xiàn)的,對OS內(nèi)核來說是透明的。這種實現(xiàn)可以利用JVM對Java線程執(zhí)行期的完全控制在JVM和Java語言上實現(xiàn)線程的同步。其實現(xiàn)也就是圖四中的2)用戶級線程(User Level Thread/ULT)。
?
?
?
圖四中,用戶級線程(User Level Threads/ULT),對OS內(nèi)核來說是透明的;內(nèi)核級線程(Kernel Level Threads/KLT)在用戶空間和內(nèi)核空間有相應(yīng)線程的對應(yīng)關(guān)系。JVM的一個實例,在內(nèi)核空間只有一個OS進(jìn)程與其對應(yīng),而Java內(nèi)部實現(xiàn)的線程對OS來說都是不可見的,是實現(xiàn)在JVM內(nèi)部的用戶級線程。
4.2 對象鎖
對JVM內(nèi)部非私有數(shù)據(jù)的保護(hù),JVM采用的是為每一個這樣的數(shù)據(jù)對象都關(guān)聯(lián)一個對象鎖,這些數(shù)據(jù)主要有堆中的對象實例和方法區(qū)中的類變量。這也就是為每一個對象關(guān)聯(lián)一個對象鎖,來實現(xiàn)監(jiān)視器機(jī)制的對某個對象的互斥訪問和基于某個條件的協(xié)作工作。類鎖的實現(xiàn)采用的是對象鎖,不同的是,對象鎖是針對java.lang.Object對象,而類鎖是針對java.lang.Class對象,也就是類的實例。
JVM內(nèi)部對每一個對象鎖都有一個相應(yīng)的計數(shù),
- 如果該計數(shù)為0,則它沒有被鎖,可以由訪問它的線程來加鎖;
- 如果該對象鎖被別的線程鎖定,則現(xiàn)在訪問它的線程被掛起等待,直到鎖定它的線程釋放該對象鎖,并且計數(shù)為0;
- 一個線程已經(jīng)擁有了一個對象的對象鎖,如果再次訪問這個對象,則可以重入,并且這個對象鎖對應(yīng)的計數(shù)加一;
- 擁有對象鎖的線程釋放對象鎖的時候,對象鎖對應(yīng)的計數(shù)減一;當(dāng)減到計數(shù)為零時,該鎖可被等待的線程競爭擁有。
由對象鎖的特性可以看出,利用JVM的對象鎖就實現(xiàn)了對被對象鎖保護(hù)對象的互斥訪問,是監(jiān)視器模型的線程的互斥實現(xiàn)。當(dāng)前鎖定一個對象的線程也可以因等待(wait)某個條件而釋放該對象鎖;擁有對象鎖的線程也可以在別的線程等待的某個條件滿足之后通知它(notify)或它們(notifyAll),這也就實現(xiàn)了線程間的協(xié)作。
目前已經(jīng)討論了JVM如何實現(xiàn)線程同步的,但是還不知道如何進(jìn)入/退出臨界區(qū),以及如何通知等待的線程實現(xiàn)線程協(xié)作的,這些都在Java語言上給予了支持,本文在下面章節(jié)具體介紹。
4.3 Java語言對線程同步的支持
Java語言上對線程同步的支持主要有對臨界區(qū)的標(biāo)識,和線程協(xié)作的支持。
4.3.1 同步方法與同步語句
Java語言對臨界區(qū)的標(biāo)識是通過同步方法(Synchronized Method)和同步語句(Synchronized Statements)實現(xiàn)的。Java線程在進(jìn)入這些同步方法或同步語句標(biāo)識的臨界區(qū)開始的地方申請被保護(hù)對象的對象鎖;離開臨界區(qū)的時候(包括出現(xiàn)異常而離開的時候)釋放掉該對象鎖;如果該對象鎖已經(jīng)被別的線程鎖定,則當(dāng)前進(jìn)入的線程被掛起等待。這一切是在JVM內(nèi)部實現(xiàn)的,Java程序中要做的是用同步方法或同步語句標(biāo)識臨界區(qū),并指名被保護(hù)對象,也就是對象鎖所對應(yīng)的對象。
同步方法是在一個類的方法的前面用synchronized關(guān)鍵字聲明,這樣標(biāo)識了一個臨界區(qū),在線程訪問這個類的對象的該方法的時候,就遵從鎖對象的管理機(jī)制。同步語句是把某條或某幾條語句用synchronized關(guān)鍵字標(biāo)識出并指名同步語句所針對的對象。
同步方法和同步語句實現(xiàn)的機(jī)理是一樣的,所不同的只是它們所標(biāo)識區(qū)域的粒度不同,同步方法的標(biāo)識的鎖的粒度大于同步語句的,線程等待該鎖的時間也就比較久,但是實現(xiàn)會比較容易,;同步方法可以指定其所管理的對象,比較靈活。所以對于同步方法或是同步語句的選擇,一般原則是,對性能要求不是很高的應(yīng)用層程序采用同步方法,而調(diào)度性能要求較高的底層應(yīng)用,宜采用同步語句,并盡量減小其所保護(hù)的范圍,當(dāng)然這在提高性能的同時增加了設(shè)計的復(fù)雜度。所以這要根據(jù)你所具體應(yīng)用場景的各項因素來平衡選擇。
4.3.2 協(xié)作——wait & notify/notifyAll
Java語言的每個對象(都是java.lang.Object的子類)都實現(xiàn)了線程協(xié)作的方法,只是這些方法只有在同步方法或同步語句所標(biāo)識的臨界區(qū)內(nèi)才能被調(diào)用,也就是調(diào)用這些方法的時候,相對應(yīng)的對象已經(jīng)被加鎖。這種協(xié)作方式,也就如前文所述,
- 擁有對象鎖(監(jiān)視器)的線程調(diào)用wait釋放該對象鎖并等待再次進(jìn)入;
- 擁有對象鎖的線程執(zhí)行過程中,別的線程等待的條件滿足,則通知(notify)等待的線程或通知所有(notifyAll)等待的線程。
wait & notify/notifyAll的原型聲明如下:
?
public final void wait() throws InterruptedException
public final native void wait(long timeout) throws InterruptedException;
public final void wait(long timeout, int nanos) throws InterruptedException
public final native void notify();
public final native void notifyAll();
?
等待某個對象鎖的時候,可以指定等待的時間,超時的話,自動退出等待。
當(dāng)明確知道等待區(qū)內(nèi)只有一個等待線程的時候,才應(yīng)該使用notify,否則就應(yīng)該使用notifyAll,讓JVM采用相應(yīng)的調(diào)度策略來決定選擇哪個等待該對象鎖的線程被喚起。這樣就可由JVM來保證避免某個線程無限制等待的饑餓現(xiàn)象,而不需要用戶來關(guān)注。
五、利用JVM線程同步解決生產(chǎn)者消費(fèi)者問題
本節(jié)以生產(chǎn)者消費(fèi)者的實例說明JVM線程同步的設(shè)計。
5.1 生產(chǎn)者消費(fèi)者問題相關(guān)類層次
生產(chǎn)者消費(fèi)者問題相關(guān)類層次如下圖所示
?
?
圖五、生產(chǎn)者消費(fèi)者問題相關(guān)類層次圖
?
生產(chǎn)者(Producer)和消費(fèi)者(Consumer)線程引用同一個緩沖區(qū)(Buffer)實例。緩沖區(qū)可以判斷當(dāng)前是否為空(isNull);非空情況下,可以從中得到產(chǎn)品并將其清空(getProduct);Buffer為空的情況下,可以放置產(chǎn)品進(jìn)去(setProduct)。
5.2 生產(chǎn)者線程——threadSynch.ProducerConsumer.Producer
生產(chǎn)者線程不停地生產(chǎn)產(chǎn)品,如果緩沖區(qū)為空,放置產(chǎn)品到緩沖區(qū)。Producer Override Thread的run()方法,這樣在調(diào)用線程的start()方法的時候,JVM線程調(diào)度機(jī)制會自動調(diào)用線程的run()方法。下面程序段是Producer.run()方法。
?
??? public void run() {
??????? super.run();
??????? while (true) {
??????????? execute();
??????? }
??? }
?
execute()是生產(chǎn)者的循環(huán)執(zhí)行體,實現(xiàn)的是圖三的生產(chǎn)者的活動,代碼如下:
??? private void execute() {
??????? generateNewProduct();
??????? System.out.println("[Producer] produced a new product: " + product);
??????? synchronized (buffer) {
??????????? System.out.println("[Producer] Owned the buffer's monitor!");
??????????? while (!buffer.isNull()) {
??????????????? System.out.println("[Producer] release the monitor and wait!");
??????????????? try {
??????????????????? buffer.wait();
??????????????? } catch (InterruptedException e) {
??????????????????? e.printStackTrace();
??????????????? }
??????????????? System.out.println("[Producer] re-owned the monitor!");
??????????? }
??????????? System.out.println("[Producer] put the product (" + product
??????????????????? + ") into the buffer!");
??????????? buffer.setProduct(product);
??????????? System.out.println("[Producer] notify other threads waiting on the monitor!");
??????????? buffer.notifyAll();
??????????? System.out.println("[Producer] to release the monitor!");
??????? }
??? }
?
generateNewProduct()利用java.util.Random產(chǎn)生一個隨機(jī)數(shù)代碼生產(chǎn)者生產(chǎn)的產(chǎn)品。
??? private void generateNewProduct() {
??????? int prd = product;
??????? while ((prd = (new Random()).nextInt(100)) == product)
??????????? ;
??????? product = prd;
??? }
?
5.3 消費(fèi)者線程——threadSynch.ProducerConsumer.Consumer
消費(fèi)者線程不停地查詢緩沖區(qū)內(nèi)是否有產(chǎn)品,如果緩沖區(qū)內(nèi)有產(chǎn)品,則從中取出產(chǎn)品并清緩沖區(qū)。Consumer Override Thread.run()方法,這樣在調(diào)用線程的start()方法的時候,JVM線程調(diào)度機(jī)制會自動調(diào)用線程的run()方法。Run()的實現(xiàn)同生產(chǎn)者線程,不同的是循環(huán)執(zhí)行體execute(),其實現(xiàn)的是圖三所示的消費(fèi)者的活動。
?
??? private void execute() {
??????? synchronized (buff) {
??????????? System.out.println("[Consumer] Owned the monitor!");
??????????? while (buff.isNull()) {
??????????????? System.out.println("[Consumer] release the monitor and wait!");
??????????????? try {
??????????????????? buff.wait();
??????????????? } catch (InterruptedException e) {
??????????????????? ??? e.printStackTrace();
??????????????? }
??????????????? System.out.println("[Consumer] re-owned the monitor!");
??????????? }
??????????? product = buff.getProduct();
??????????? System.out.println("[Consumer] get a product (" + product
??????????????????? + ") from the buffer!");
??????????? System.out.println("[Consumer] notify others waiting on the monitor!");
??????????? buff.notifyAll();
??????????? System.out.println("[Consumer] to release the monitor!");
??????? }
??????? System.out.println("[Consumer] consume the product: " + product);
??? }
?
5.4 主程序——threadSynch.ProducerConsumer. ProducerConsumer
主程序ProducerConsumer創(chuàng)建Buffer以及生產(chǎn)者和消費(fèi)者線程的實例,并啟動生產(chǎn)者和消費(fèi)者線程。
?
package threadSynch.ProducerConsumer;
public class ProducerConsumer {
??? private static Producer producer;
??? private static Consumer consumer;
??? private static Buffer buffer;
??? public static void main(String[] args) {
?????? buffer = new Buffer(0);
?????? producer = new Producer(buffer);
?????? consumer = new Consumer(buffer);
?????? consumer.start();
?????? producer.start();
??? }
}
?
5.5 程序運(yùn)行時的一個快照
下面是上述程序運(yùn)行時的一個快照,并分別以綠色和藍(lán)色標(biāo)識生產(chǎn)者和消費(fèi)者的輸出:
?
[Consumer] Owned the monitor!?????????????????????????????????????????????? (01)
[Consumer] release the monitor and wait!??????????????????????????????????? (02)
[Producer] produced a new product: 43??????????? ???????????????????????????(03)
[Producer] Owned the buffer's monitor!????????????????????????????????????? (04)
[Producer] put the product (43) into the buffer!??????????????????????????? (05)
[Producer] notify other threads waiting on the monitor!??? ?????????????????(06)
[Producer] to release the monitor!????????????????????????????????????????? (07)
[Consumer] re-owned the monitor!??????????????????????????????????????????? (08)
[Consumer] get a product (43) from the buffer!?????????????????????? ???????(09)
[Consumer] notify others waiting on the monitor!??????????????????????????? (10)
[Consumer] to release the monitor!????????????????????????????????????????? (11)
[Consumer] consume the product: 43????????????????????????????????????????? (12)
[Consumer] Owned the monitor!?????????????????????????????????????????????? (13)
[Producer] produced a new product: 70?????????????????????????????????????? (14)
[Consumer] release the monitor and wait!??????????????????????????????????? (15)
[Producer] Owned the buffer's monitor!????????????????????????????????????? (16)
[Producer] put the product (70) into the buffer!??????????????????????????? (17)
[Producer] notify other threads waiting on the monitor!???????????????????? (18)
[Producer] to release the monitor!????????????????????????????????????????? (19)
[Producer] produced a new product: 43?????????????????????????????????????? (20)
[Producer] Owned the buffer's monitor!????????????????????????????????????? (21)
[Producer] release the monitor and wait!??????????????????????????????????? (22)
[Consumer] re-owned the monitor!??????????????????????????????????????????? (23)
[Consumer] get a product (70) from the buffer!????????????????????????????? (24)
[Consumer] notify others waiting on the monitor!??????????????????????????? (25)
[Consumer] to release the monitor!????????????????????????????????????????? (26)
[Consumer] consume the product: 70????????????????????????????????????????? (27)
[Consumer] Owned the monitor!????????????????? ?????????????????????????????(28)
[Consumer] release the monitor and wait!??????????????????????????????????? (29)
[Producer] re-owned the monitor!??????????????????????????????????????????? (30)
[Producer] put the product (43) into the buffer!???????? ???????????????????(31)
[Producer] notify other threads waiting on the monitor!???????????????????? (32)
[Producer] to release the monitor!????????????????????????????????????????? (33)
[Producer] produced a new product: 34????????????????????????????? ?????????(34)
[Producer] Owned the buffer's monitor!????????????????????????????????????? (35)
[Producer] release the monitor and wait!??????????????????????????????????? (36)
[Consumer] re-owned the monitor!??????????????????????????????????????????? (37)
[Consumer] ――――――?????????????????????????????????????????????????????????? (38)
[Producer] ――――――?????????????????????????????????????????????????????????? (39)
?
生產(chǎn)者消費(fèi)者相關(guān)聯(lián)的對象的簡寫表示:Per-生產(chǎn)者;Cer-消費(fèi)者;B-緩沖區(qū);M-緩沖區(qū)相關(guān)聯(lián)的監(jiān)視器;Px(x = 1, 2, …)-產(chǎn)品。結(jié)合圖三的生產(chǎn)者和消費(fèi)者的活動圖,可以解釋上面的輸出快照。
- 消費(fèi)者Cer首先運(yùn)行,獲得監(jiān)視器M,因緩沖區(qū)B內(nèi)當(dāng)前還沒有產(chǎn)品P,所以Cer釋放M并等待[Line 1, 2]。
- 生產(chǎn)者Per生產(chǎn)了一個產(chǎn)品P1,在獲得M之后,放置P1到B并通知Cer,釋放M[Line 3-7]。
- Cer重新獲得M之后,從B中得到P1并清空B,通知其它線程當(dāng)前B可用,釋放M,消費(fèi)產(chǎn)品P1(因為P1已經(jīng)被Cer獲得,所以消費(fèi)P1不需要在臨界區(qū)內(nèi)完成),重新獲得M(線程調(diào)度的作用)[Line 8-13]。
- Per生產(chǎn)一個新產(chǎn)品P2(雖然Per當(dāng)前未獲得M,但生產(chǎn)產(chǎn)品是不需要在臨界區(qū)的,當(dāng)JVM線程調(diào)度運(yùn)行Per的時候,它仍然可以生產(chǎn)產(chǎn)品)[Line 14]。
- P2并未被放到B,所以Cer釋放M并等待[Line 15]。
- Per現(xiàn)在可獲得M[16],并放置P2到B[17],然后喚醒等待M的線程[18]并釋放M[19];當(dāng)前Cer并未獲得調(diào)度運(yùn)行,所以Per繼續(xù)生產(chǎn)P3[20];Per獲得M并試圖放置P3到B[21],但此時Cer還未取走P2,所以Per釋放M并等待[22];
- Cer…[23, …]
- Per…[line…]
<!--[if !supportLineBreakNewLine]-->
<!--[endif]-->
六、總結(jié)
多線程有有效的利用了計算機(jī)的最珍貴的CPU資源,但在計算性能提高的基礎(chǔ)上的同時,也增加了設(shè)計的復(fù)雜度。Java利用JVM對運(yùn)行期的控制,實現(xiàn)了JVM內(nèi)的線程模型,并簡化了實現(xiàn)了線程間的同步問題,對編寫Java程序的人員來說,開發(fā)的效率顯著提高。
任何設(shè)計決策都不能用絕對的孤立的評價角度來看,它應(yīng)該是各種因素的綜合。JVM線程同步模型也是一樣,Java開發(fā)者實現(xiàn)簡單的代價也就喪失了靈活性。比如要在同一個類的多個方法之間實現(xiàn)同步,用同步方法或同步語句來實現(xiàn)就顯得力不從心了,如果增加監(jiān)視器來實現(xiàn),處理不當(dāng)就又可能引起死鎖(Dead Lock)。
關(guān)于其它同步方法,Doug?Lea實現(xiàn)了一個Java語言工具包?,F(xiàn)在這個包已經(jīng)加入處于JCP控制下的JSR標(biāo)準(zhǔn)。SUN Java SE 1.5實現(xiàn)中也已經(jīng)加入了這部分代碼,有興趣的讀者可以參閱。
文中所描述的生產(chǎn)者消費(fèi)者問題的實例代碼可與本文作者聯(lián)系索取。
?
參考資料及進(jìn)一步閱讀
1) 孫鐘秀,費(fèi)翔林,駱斌,謝立. 操作系統(tǒng)教程,第三版. 高等教育出版社,2003.8
2) Abraham Silberschatz, Peter Baer Galvin, Greg Gagne. Operating System Concepts, 6th Edition. John Wiley & Sons, Inc/高等教育出版社影印, 2002.5
3) David R. Butenhof/于磊,曾剛. Programming with POSIX Threads. Addison Wesley/中國電力出版社, 2003
4) Bill Venners著/曹曉鋼,蔣靖譯. Inside the Java Virtual Machine, 2nd edition. McGraw-Hill/機(jī)械工業(yè)出版社, 2003
5) Ken Arnold, James Gosling, David Holmes. The Java Programming Language, 3rd Edition. Addison Wesley/中國電力出版社影印, 2003.5
6) Michael L. Scott著/裘宗燕譯. Programming Language Pragmatics. Elsevier/電子工業(yè)出版社, 2005.3
關(guān)于作者
田海立,碩士,國家系統(tǒng)分析師,CSAI專業(yè)顧問。您可以通過 haili.tian@csai.cn 或 haili.tian@gmail.com 與他聯(lián)系,到 http://blog.csdn.net/thl789/ 看他最新的文章。
?
(本文可自由轉(zhuǎn)載,但請給出原文鏈接: http://blog.csdn.net/thl789/archive/2005/12/30/566494.aspx)。
?
總結(jié)
以上是生活随笔為你收集整理的解析JVM线程同步机制的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: velocity显示List与Map的方
- 下一篇: FilterDispatcher is