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