寫(xiě)在前面 ◆ ◆ ◆ ◆ 并行程序開(kāi)發(fā)將不可避免地要涉及多線程、多任務(wù)間的寫(xiě)作和數(shù)據(jù)共享等問(wèn)題。在JDK中,提供了多種途徑實(shí)現(xiàn)多線程間的并發(fā)控制。常用的方法有:內(nèi)部鎖、重入鎖、讀寫(xiě)鎖、信號(hào)量等 Java內(nèi)存模型與volatile ◆ ◆ ◆ ◆ Java中每一個(gè)線程有一塊工作內(nèi)存區(qū),其中存放著被所有線程共享的主內(nèi)存中的變量的值的拷貝。當(dāng)線程執(zhí)行時(shí),它在自己的工作內(nèi)存中操作著這些變量。為了存取一個(gè)共享的變量,一個(gè)線程通常先獲取鎖定并且清除它的工作內(nèi)存區(qū),這保證該共享變量從所有線程的共享內(nèi)存區(qū)正確地裝入到線程的工作內(nèi)存區(qū),當(dāng)線程解鎖時(shí)保證該工作內(nèi)存區(qū)中變量的值寫(xiě)回到共享內(nèi)存中。 一個(gè)線程可以執(zhí)行的操作有使用(user),賦值(assign),裝載(load),存儲(chǔ)(store),鎖定(lock),解鎖(unlock)。而主內(nèi)存可以執(zhí)行的操作有讀(read),寫(xiě)(write),鎖定(lock),解鎖(unlock),每一個(gè)操作都是原子的。如下圖: 當(dāng)一個(gè)線程使用某一個(gè)變量時(shí),不論程序是否正確地使用線程同步操作,它獲取的值一定時(shí)由它本身或者其他線程存儲(chǔ)到變量中的值。例如,如果兩個(gè)線程把不同值或者對(duì)象引用存儲(chǔ)到同一個(gè)共享變量中,那么該變量的值要么是這個(gè)線程的,要么是另一個(gè)線程的,共享變量的值不會(huì)由兩個(gè)線程的引用值組合而成(除long,double外)。 一個(gè)變量是Java程序可以存取的一個(gè)地址,它不僅包括基本類型變量、引用類型變量,還包括數(shù)據(jù)類型變量。保存在主內(nèi)存區(qū)的變量可以被所有線程共享,但一個(gè)線程存取另一個(gè) 線程的參數(shù)或者局部變量是不可能的。 由于每個(gè)線程都有自己的工作內(nèi)存區(qū),因此當(dāng)一個(gè)線程改變自己的工作內(nèi)存中的數(shù)據(jù)時(shí),對(duì)其他線程來(lái)說(shuō),可能是不可見(jiàn)的。因此,可以使用volatile關(guān)鍵字迫使所有線程均讀寫(xiě)主內(nèi)存中的對(duì)應(yīng)變量,從而使得volatile變量在多線程間可見(jiàn)。 聲明volatile的變量可以做到如下保證:
同步關(guān)鍵字synchronized ◆ ◆ ◆ ◆ 同步關(guān)鍵字synchronized使用簡(jiǎn)潔,代碼可維護(hù)性好。在JDK6中,性能也比早期的JDK由很大改進(jìn),如果可以滿足程序要求,可以首先考慮這種同步方式。 synchronized最常用的方法是鎖定一個(gè)對(duì)象的方法;也可以同步塊,與同步方法相比,可以更為精確地控制代碼的范圍,有利于鎖的快進(jìn)快出,提高吞吐量。 當(dāng)synchronized用于static函數(shù)時(shí),相當(dāng)于將鎖加到當(dāng)前Class對(duì)象上,因此,所有對(duì)該方法的調(diào)用,都必須獲得Class對(duì)象的鎖。 ReentrantLock重入鎖 ◆ ◆ ◆ ◆ ReentrantLock比內(nèi)部鎖synchronized擁有更強(qiáng)大的功能,它可以中斷,可定時(shí)。JDK5中,在高并發(fā)的情況下,它比synchronized有明顯的性能優(yōu)勢(shì)。在JDK6中,由于JVM的優(yōu)化,兩者差別不是很大。 ReentrantLock還提供了公平和非公平的兩種鎖。公平鎖可以保證鎖的等待隊(duì)列中的各個(gè)線程是公平的,不會(huì)出現(xiàn)插隊(duì)的情況,對(duì)鎖的獲取總是先進(jìn)先出,而非公平的就不做這個(gè)保證,申請(qǐng)鎖的線程可以插隊(duì)。公平鎖的實(shí)現(xiàn)代價(jià)比非公平的大,因此從性能上,非公平鎖的性能要好得多。因此若無(wú)特殊的需求,應(yīng)該優(yōu)先考慮非公平鎖。 使用ReentrantLock時(shí),一定要牢記,在程序最后釋放鎖。一般釋放鎖的代碼要寫(xiě)在finally里,否則如果程序出現(xiàn)異常,鎖將無(wú)法釋放了。相比synchronized,JVM總是會(huì)在最后自動(dòng)釋放synchronized鎖。 ReadWriteLock讀寫(xiě)鎖 ◆ ◆ ◆ ◆ ReadWriteLock讀寫(xiě)鎖是JDK5中提供的讀寫(xiě)分離鎖。讀寫(xiě)分離鎖可以有效地幫助減少鎖競(jìng)爭(zhēng),以提高系統(tǒng)性能。 比如線程A1,A2,A3進(jìn)行寫(xiě)操作,B1,B2,B3進(jìn)行讀操作,如果市容重入鎖或者內(nèi)部鎖,則理論上所有讀之間、讀寫(xiě)之間、寫(xiě)寫(xiě)之間都是串行操作。當(dāng)A1進(jìn)行讀取時(shí),A2,A3則需要等待鎖。由于讀操作并不對(duì)數(shù)據(jù)的完整性造成破壞,這種等待顯然是不合理的。因此讀寫(xiě)鎖就有發(fā)揮的余地。這種情況下,讀寫(xiě)鎖允許多個(gè)線程同時(shí)讀,使得B1,B2,B3之間真正并行。但是考慮到數(shù)據(jù)完整性,寫(xiě)寫(xiě)操作和讀寫(xiě)操作間依然需要互相等待和持有鎖。 如果在系統(tǒng)中,讀操作的次數(shù)遠(yuǎn)遠(yuǎn)大于寫(xiě)操作,則讀寫(xiě)鎖可以發(fā)揮最大的功效。 Condition對(duì)象 ◆ ◆ ◆ ◆ 線程間的協(xié)調(diào)工作光有鎖是不夠的,在業(yè)務(wù)層,可能會(huì)有復(fù)雜的線程間寫(xiě)作的邏輯。Conditon對(duì)象就可以用于協(xié)調(diào)多線程的復(fù)雜協(xié)作。 Conditon是與鎖相關(guān)聯(lián)的。通過(guò)Lock接口的Conditon newConditon()方法可以生成一個(gè)與鎖綁定的Conditon實(shí)例。Conditon對(duì)象和鎖的關(guān)系,就如同Object.wait()、notify()兩個(gè)函數(shù)和synchronized關(guān)鍵字一樣,它們可以配合使用以完成對(duì)多線程的協(xié)調(diào)控制。 Semaphore信號(hào)量 ◆ ◆ ◆ ◆ 信號(hào)量為多線程寫(xiě)作提供了更多強(qiáng)大的控制方法。廣義上說(shuō),信號(hào)量是對(duì)鎖的擴(kuò)展。無(wú)論是內(nèi)部鎖synchronized還是重入鎖ReentrantLock,一次都只允許一個(gè)線程訪問(wèn)一個(gè)資源,而信號(hào)量卻可以指定多個(gè)線程同時(shí)訪問(wèn)某一個(gè)資源。 ThreadLocal線程局部變量 ◆ ◆ ◆ ◆ ThreadLocal線程局部變量是一種多線程間并發(fā)訪問(wèn)變量的解決方案。與synchronized等加鎖的方法不同,ThreadLocal完全不提供鎖,而使用以空間換時(shí)間的手段,為每個(gè)線程提供變量的獨(dú)立副本,以保證線程安全,因此它不是一種數(shù)據(jù)共享的解決方案。 從性能上看,ThreadLocal并不具有絕對(duì)的優(yōu)勢(shì),在并發(fā)量不是很高時(shí),也許加鎖的性能可能會(huì)更好。但是作為一套與鎖無(wú)關(guān)的線程安全按解決方案,在高并發(fā)量或者鎖競(jìng)爭(zhēng)激烈的場(chǎng)合,使用ThreadLocal可以在一定程度上減少競(jìng)爭(zhēng)。 |
|
來(lái)自: 西北望msm66g9f > 《編程》