黃 慶
(武漢交通職業(yè)學(xué)院,湖北 武漢 430065)
Java同步線程模型分析與改進(jìn)*
黃 慶
(武漢交通職業(yè)學(xué)院,湖北 武漢 430065)
Java是為數(shù)不多的提供內(nèi)置多線程機(jī)制的編程語(yǔ)言。Java多線程程序雖然能夠提高計(jì)算機(jī)資源的使用效率及處理速度,但其同步多線程模型還存在一定的缺陷。特別是多線程應(yīng)用系統(tǒng)程序設(shè)計(jì)時(shí),如果算法和策略不當(dāng),反而會(huì)引起死鎖等問(wèn)題。文章分析Java同步多線程模型的優(yōu)缺點(diǎn),并對(duì)Java多線程應(yīng)用系統(tǒng)的死鎖問(wèn)題提出改進(jìn)策略。
Java線程模型;同步;死鎖;改進(jìn)
很多編程語(yǔ)言,如C語(yǔ)言和C++語(yǔ)言都不提供內(nèi)置的多線程機(jī)制,要實(shí)現(xiàn)并發(fā)處理,需要調(diào)用操作系統(tǒng)的多線程原語(yǔ)操作。Java類庫(kù)中本身包含多線程原語(yǔ)操作,編寫應(yīng)用程序時(shí)可以直接使用內(nèi)置的多線程操作以實(shí)現(xiàn)并發(fā)處理。多線程程序能夠有效提高計(jì)算機(jī)資源的使用效率,提高處理速度。但Java多線程程序在并行處理的同時(shí),也會(huì)產(chǎn)生死鎖等問(wèn)題,特別是對(duì)于具有“生產(chǎn)者/消費(fèi)者”之類的并發(fā)處理程序。Java防止產(chǎn)生死鎖的方法是采用線程同步操作。
1.1 Java引入同步線程的優(yōu)點(diǎn)
引入同步線程模型,系統(tǒng)并發(fā)執(zhí)行程度可以大大提高,編程中并發(fā)程序產(chǎn)生的問(wèn)題與局限性能得到有效的控制。這種線程概念的應(yīng)用,和多進(jìn)程模式相比較,Java同步線程模型有更多優(yōu)點(diǎn)。二者的區(qū)別在于,進(jìn)程的兩個(gè)單位分開(kāi)。線程中,每個(gè)執(zhí)行單元,既可以獨(dú)立調(diào)度,又可以各自分派。每個(gè)進(jìn)程只是一個(gè)獨(dú)立出來(lái)的控制流,而非可獨(dú)立擁有資源的基本單位。因此,操作時(shí)可以減少對(duì)獨(dú)立擁有資源的單位做頻率較高的切換,程序并發(fā)執(zhí)行程度的功能也因此得到較大的突破。
可擁有資源的獨(dú)立單位和可獨(dú)立調(diào)度及分派的基本單位,是進(jìn)程并發(fā)執(zhí)行的必要條件,這兩種屬性在傳統(tǒng)的操作系統(tǒng)也得到體現(xiàn)。進(jìn)程的這兩種屬性,為進(jìn)程并發(fā)執(zhí)行起到關(guān)鍵作用。但是,為使程序并發(fā)得到有效執(zhí)行,創(chuàng)建、撤銷等操作必須在系統(tǒng)內(nèi)實(shí)施,因?yàn)樽鳛橘Y源的擁有者,不同進(jìn)程所擁有的內(nèi)存空間堆棧不同,所以系統(tǒng)在進(jìn)行這些操作過(guò)程中,一定要給其騰出更大的時(shí)空。基于此,系統(tǒng)中所設(shè)置的進(jìn)程數(shù)目不應(yīng)該過(guò)多,進(jìn)程在切換的次數(shù)也不宜太高,從這點(diǎn)看來(lái),對(duì)并發(fā)進(jìn)程的進(jìn)一步發(fā)展是一種限制。而值得注意的是,Java同步線程模式,難點(diǎn)在于攻克并發(fā)程序設(shè)計(jì)中出現(xiàn)的各種問(wèn)題,系統(tǒng)中內(nèi)置多個(gè)進(jìn)程或線程,并發(fā)執(zhí)行的進(jìn)程和線程又是各自獨(dú)立,必會(huì)導(dǎo)致相互間出現(xiàn)資源共享關(guān)系、相互協(xié)作關(guān)系。
Java作為多線程模式語(yǔ)言,具有同時(shí)實(shí)行多個(gè)線程和實(shí)施多個(gè)操作功能,并且多線程技術(shù)還可以增強(qiáng)圖形用戶界面之間的交互能力,當(dāng)前的主流操作系統(tǒng)如Windows等,都采用了這種線程的概念,即把線程視為基本執(zhí)行單位。在普通程序中,僅有一個(gè)執(zhí)行程序,許多情況沒(méi)法或者極難處理。例如,在GUI應(yīng)用程序中,一般都希望在進(jìn)行后臺(tái)計(jì)算的同時(shí)能響應(yīng)用戶輸入。因此,應(yīng)用程序在執(zhí)行時(shí)要實(shí)行中斷計(jì)算,檢測(cè)用戶輸入的狀況,這樣必然存在危險(xiǎn),而Java的多線程操作可避免這種情況。
1.2 Java內(nèi)存模型對(duì)同步多線程的支持
作為平臺(tái)無(wú)關(guān)的編程語(yǔ)言,Java語(yǔ)言規(guī)范定義了一個(gè)統(tǒng)一的內(nèi)存管理模型(JMM),[1]具體由Java虛擬機(jī)(JVM)來(lái)實(shí)現(xiàn)。JMM將線程能訪問(wèn)的內(nèi)存劃分為主內(nèi)存(main memory)和工作內(nèi)存(working memory)。所有的線程都能對(duì)主內(nèi)存中的數(shù)據(jù)進(jìn)行并發(fā)訪問(wèn)。而每一個(gè)線程都有其私有的其他線程不能訪問(wèn)的工作內(nèi)存,其中存放的是該線程從主內(nèi)存中拷貝過(guò)來(lái)的變量以及訪問(wèn)方法所取得的局部變量。每個(gè)線程對(duì)變量的操作,都是先從主內(nèi)存將其拷貝到工作內(nèi)存,再對(duì)其進(jìn)行操作,各線程之間想要讀取或修改對(duì)方所訪問(wèn)的變量均需要通過(guò)主內(nèi)存。當(dāng)需要修改變量值時(shí),先從主內(nèi)存復(fù)制到工作內(nèi)存,修改其值后將其拷貝回主內(nèi)存。
多線程并發(fā)執(zhí)行時(shí),在資源上形成共享與合作關(guān)系。由于進(jìn)程線程之間相互協(xié)作,需要共同完成目標(biāo)操作。但是,如果線程和進(jìn)程的協(xié)作關(guān)系出現(xiàn)了矛盾,結(jié)果將不堪設(shè)想,會(huì)給整個(gè)系統(tǒng)帶來(lái)危險(xiǎn)。比如,對(duì)具有“生產(chǎn)者/消費(fèi)者”之類的并發(fā)處理程序,Java多線程程序在并行處理的同時(shí),也會(huì)產(chǎn)生死鎖等問(wèn)題。所謂死鎖,即當(dāng)兩個(gè)或多個(gè)線程彼此無(wú)限期等待,導(dǎo)致都不能繼續(xù)執(zhí)行的狀態(tài)。Java同步線程解決這一問(wèn)題的方法是使用synchronized關(guān)鍵字,對(duì)該對(duì)象完成鎖操作,并且在完成鎖操作前停止處理。鎖操作完成時(shí),synchronized語(yǔ)句體得到執(zhí)行;當(dāng)語(yǔ)句體執(zhí)行完畢,無(wú)論正?;虍惓?,解鎖操作自動(dòng)完成。如程序add Number 和print方法均是synchronized方法,Java就會(huì)自動(dòng)地在一個(gè)線程執(zhí)行synchronized方法時(shí),禁止其他線程執(zhí)行它。只有當(dāng)所有synchronized方法結(jié)束后,等待的線程才能執(zhí)行。
Java線程訪問(wèn)標(biāo)識(shí)為Synchronized的方法,并對(duì)相應(yīng)變量做操作時(shí),JVM執(zhí)行過(guò)程的描述如圖1所示。
圖1 JVM的執(zhí)行過(guò)程
synchronized關(guān)鍵字可以保證它鎖住的代碼區(qū)訪問(wèn)的唯一性,即任何時(shí)刻只允許唯一的一個(gè)線程單獨(dú)訪問(wèn)同步區(qū)。[2]如果不使用synchronized關(guān)鍵字,則JVM就不能保證讀取數(shù)據(jù)和寫回?cái)?shù)據(jù)按上述次序執(zhí)行,從而可能引起程序數(shù)據(jù)混亂。
Java編程語(yǔ)言的線程模型是該語(yǔ)言的薄弱環(huán)節(jié),同實(shí)際復(fù)雜程序的要求并不完全相適應(yīng)。Java語(yǔ)言本身對(duì)線程的語(yǔ)法和類包的支持比較少,只能適用某些小型應(yīng)用環(huán)境?;谶@些缺陷,發(fā)掘Java多線程更多的實(shí)現(xiàn)方法,處理由多線程產(chǎn)生的數(shù)據(jù)死鎖,顯得尤為重要。
因?yàn)椴l(fā)線程會(huì)競(jìng)爭(zhēng)程序中的資源,共享資源就必須得到均衡分配,讓每線程在程序執(zhí)行訪問(wèn)過(guò)程中,能充分利用有限的資源。由于Java中并不對(duì)死鎖提供必要的檢測(cè),系統(tǒng)自身也沒(méi)有死鎖線程的設(shè)置,因此對(duì)Java程序員來(lái)說(shuō),算法上要防止產(chǎn)生死鎖。許多線程程序設(shè)計(jì)可以歸納出死鎖產(chǎn)生的條件。例如,兩個(gè)或兩個(gè)以上的synchronized方法互相依存,相互之間無(wú)限期等待,以至于所有線程均無(wú)法繼續(xù),導(dǎo)致死鎖。如下例:
class Printer implements Runnable {
Input input ;
synchronized void print ( ) {
input . write ( ) ;
}
synchronized void sendData ( ) {
}
public void run ( ) {
print ( ) ;
}
}
class Input implements Runnable {
Printer printer;
synchronized void write ( ) {
printer. sendData ( ) ;
}
public void run ( ) {
write ( ) ;
}
}
class Deadlock {
public static void main ( string args [ ] ) {
Printer P= new Printer ( ) ;
Input I= new Input ( ) ;
P·input= I;
I·printer= P;
Thread t1= new Thread (P) ;
Thread t2= new Thread (I) ;
t1·start ( ) ;
t2·start ( ) ;
}
}
由編程看,每一個(gè)對(duì)象有一個(gè)與其相關(guān)的monitor對(duì)象。monitor的作用類似于看門人,每次僅允許一個(gè)synchronized方法進(jìn)入,當(dāng)該synchronized方法結(jié)束后,monitor解鎖,另一個(gè)synchronized方法開(kāi)始執(zhí)行。如果程序中有幾個(gè)競(jìng)爭(zhēng)資源的并發(fā)線程,那么保證均衡是很重要的,其中系統(tǒng)均衡是指每線程在執(zhí)行過(guò)程中都能充分訪問(wèn)有限的資源,而且系統(tǒng)中沒(méi)有死鎖的線程。
同步線程中,許多線程在執(zhí)行中必須考慮與其他線程之間共享數(shù)據(jù)或協(xié)調(diào)執(zhí)行狀態(tài),而在Java中的每個(gè)對(duì)象都有一把鎖與之對(duì)應(yīng),但Java不提供單獨(dú)的lock和unlock操作,它由高層的結(jié)構(gòu)隱式實(shí)現(xiàn),來(lái)保證操作的對(duì)應(yīng)。
上述編程,運(yùn)行Printer對(duì)象和Input對(duì)象的線程執(zhí)行時(shí)會(huì)造成死鎖,因?yàn)檫@些線程交替執(zhí)行的方式不正確。具體來(lái)講,Printer對(duì)象為執(zhí)行其printer方法,鎖住了它的monitor;隨后Input對(duì)象為執(zhí)行其write方法,也鎖住了自己的monitor;而后printer方法需調(diào)用Input對(duì)象的write方法,但I(xiàn)nput對(duì)象的monitor已經(jīng)加鎖,因此printer方法等待。同時(shí)Input對(duì)象需加鎖printer對(duì)象的monitor,以執(zhí)行printer對(duì)象的sendData方法,printer對(duì)象的monitor已經(jīng)加鎖,因此Input對(duì)象也需等待。這樣造成彼此無(wú)限期等待,兩個(gè)線程均不能繼續(xù)(如圖2)。
圖2 兩線程的“死鎖”執(zhí)行狀態(tài)
可采用一些簡(jiǎn)單的策略和算法解決死鎖問(wèn)題,編制出正確線程程序。首先,完成給定的目標(biāo)任務(wù),并且和其他任務(wù)同時(shí)運(yùn)行,多線程就需要應(yīng)用在這類編程中。此時(shí),可明確控制每個(gè)線程完成的功能,線程同步位置在代碼中也就找到確定的位置。在上例中,大可不必應(yīng)用兩個(gè)線程去實(shí)行操作,因?yàn)樗鼈冎g關(guān)系是互相依賴,當(dāng)從一個(gè)synchronized方法中調(diào)用另一個(gè)synchronized方法時(shí),要謹(jǐn)慎。因?yàn)槿绻蓛蓚€(gè)或兩個(gè)以上的線程能夠?qū)@些方法獨(dú)立實(shí)施時(shí),可能導(dǎo)致數(shù)據(jù)死鎖狀態(tài)。在這種情況下,即使用同步多線程技術(shù),加鎖時(shí)間也應(yīng)當(dāng)縮短到最少。如果目標(biāo)完成需要較長(zhǎng)時(shí)間,最好不要加鎖。防止死鎖的另一種方法是,根據(jù)每個(gè)線程所需的資源數(shù)目,給競(jìng)爭(zhēng)的資源編號(hào),讓它運(yùn)行時(shí)先得到序號(hào)較小的資源,然后再進(jìn)行大序號(hào)資源的爭(zhēng)奪。[3]
Java語(yǔ)言編程技術(shù)越來(lái)越成熟,其同步線程模型改進(jìn)也將逐步完善。本文通過(guò)對(duì)Java語(yǔ)言內(nèi)置多線程機(jī)制和對(duì)引入的有效同步機(jī)制的優(yōu)點(diǎn)和缺陷進(jìn)行具體論述,來(lái)探討解決多線程序并發(fā)程序異常問(wèn)題的解決方法。相信隨著新科技的發(fā)展,Java同步線程模型將會(huì)有突破性的發(fā)展。
[1]周志遠(yuǎn),張大方,繆力.基于Java內(nèi)存模型的并發(fā)程序模型檢測(cè)[J].計(jì)算機(jī)工程與科學(xué),2010,(3):111-123.
[2]胡雯,趙海廷.JAVA多線程同步問(wèn)題研究[J].軟件導(dǎo)刊,2007,(1):98-99.
[3]于利前,王林章,雷斌,等.靜動(dòng)態(tài)結(jié)合的Java程序不變性分析方法[J].計(jì)算機(jī)學(xué)報(bào),2010,(4):736-745.
2015-03-12
黃 慶(1977-),女,湖北武漢人,武漢交通職業(yè)學(xué)院電子與信息工程學(xué)院講師,主要從事計(jì)算機(jī)程序設(shè)計(jì)、項(xiàng)目開(kāi)發(fā)的教學(xué)與研究。
10.3969/j.issn.1672-9846.2015.02.022
TP311.10
A
1672-9846(2015)02-0084-03