Chapter 3. Data synchronization
在
第二章中介紹了如何創建線程對象、啟動和終止線程。但多線程編程的關鍵在于多個線程之間數據的共享和同步,從這一章開始,將詳細介紹線程之間數據的共享和同步的各種方法。
3.1 The Synchronized Keywor 1. synchronized是Java中最基本也最常用的用來編寫多線程安全代碼的關鍵字,用以保護對多線程共享的數據的操作總是完整的;
2.
Atomic: 當一個操作被定義成原子操作時,意味著該操作在執行過程中不會被打斷;原子操作可以由硬件保證或者通過軟件來模擬;
3.
Mutex Lock: 在很多多線程系統,通過互斥鎖來保護的共享數據。Java中的任何一個對象都有一個與之相關的鎖,當一個方法被聲明成synchronized時,就表示當線程進入該方法前,必須獲得相應對象的鎖,在執行完畢再釋放這個鎖。從而保證同一時刻只有一個線程調用該對象上的被聲明為synchronized的方法。注意:Java的互斥鎖只能加在對象級上,獲得某個對象的鎖,并不能保證該對象的屬性和其它非synchronized的方法是線程安全的;也不能保證受保護的方法里調用的其它對象是多線程安全的,除非任何調用這些沒有被保護的方法或者對象只通過受保護的方法進行調用。所以,編寫線程安全的代碼關鍵就在于規劃方法和對象之間的調用關系,并盡量采用相同對象的鎖來進行同步控制。
3.2 The Volatile Keyword 1. Scope of a Lock: 鎖的作用范圍即獲得和釋放鎖之間的那段時間。
2. Java標準雖然聲明存取一個非long和double變量的操作是原子操作,但由于不同虛擬機實現的差異,在多線程環境下每個線程可能會保留自己的工作拷貝,而導致變量的值產生沖突。為了避免這種情況的發生,可以有兩種方法:
1) 為變量創建聲明為synchronized的setter和getter方法,然后任何調用(包括在類類部)該變量的地方都通過setter和getter方法;
2) 采用volatile聲明,確保每次存取這些屬性時都從主內存中讀入或者寫入主內存中;
3) volatile僅僅用于解決Java內存模式導致的問題,只能運用在對該變量只做一個單一裝載或寫入操作且該方法的其它操作并不依賴該變量的變化。如:在一個循環體中作為遞增或遞減變量時就不能使用volatile來解決線程同步的問題。
3. volatile的使用是有限的,一般而言,僅僅將其作為強制虛擬機總是從主內存讀寫變量的一個手段,或者某些需要其參數聲明為volatile的函數。
3.3 More on Race Conditions 本節以打字游戲中顯示成績的例子來解釋了可能存在的race condition。關鍵要注意以下幾點:
1. 操作系統會隨機的切換多個線程的運行,因此當多個線程調用同一個方法時,可能在
任何地方被暫停而將控制權交給另外的線程;
2. 為了減少被誤用的可能,總是假設方法有可能被多個線程調用;
3. 鎖僅僅與某個特定實例相關,而與任何方法和類都無關,這一點當需要存取類屬性或方法時要特別注意;
4. 任何時候只有一個線程能夠運行某個類中一個被聲明為synchronized的靜態方法;一個線程只能運行某個特定實例中一個被聲明為synchronized的非靜態方法。
3.4 Explicit Locking 1. 學過Win32下編寫多線程的朋友剛開始可能會被Java的Synchronized關鍵詞搞糊涂。因為Java中的任何一個對象都有一個與之相關的鎖,而不象在Win32下要先定義一個互斥量,然后再調用一個函數進入或者離開互斥區域。在JDK 1.5以后也開始提供這種顯示聲明的鎖。JDK 1.5中定義了一個Lock接口和一些類,允許程序員顯示的使用鎖對象。
2. 在Lock接口里有兩個方法:lock()和unlock()用來獲取和釋放鎖對象,從而保證受保護的代碼區域是線程安全的。
3. 使用鎖對象的一個好處在于可以被保存、傳遞和拋棄,以在比較復雜的多線程應用中使用統一的鎖。
在使用鎖對象時,總是將lock()和unlock()調用包含在try/finally塊中,以防止運行時異常的拋出而導致死鎖的情況。
3.5 Lock Scope 利用lock()和unlock()方法,我們可以在任何地方使用它們,從一行代碼到跨越多個方法和對象,這樣就能根據程序設計需要來定義鎖的作用(scope of lock)范圍,而不是象以前局限在對象的層次上。
3.5.1 Synchronized Blocks 1. 利用synchronized關鍵字也能建立同步塊,而不一定非得同步整個方法。
2. 同步一段代碼時,需要明確的指定獲取哪個對象的鎖(這就類似于鎖對象了),這樣,就可以在多個方法或對象中共享這個鎖對象。
3.6 Choosing a Locking Mechanism 使用鎖對象還是同步關鍵字(synchronized),這個取決于開發員自己。使用synchronized比較簡單,但相對而言,比較隱晦,在比較復雜的情況下(如要同時同步靜態和非靜態方法時),沒有鎖對象來得直觀和統一。一般而言,synchronized簡單,易于使用;但同時相對而言效率較低,且不能跨越多個方法。
3.6.1 The Lock Interface
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit)
throws InterruptedException;
void unlock();
Condition newCondition();
}
其中的tryLock()和tryLock(long, TimeUnit)將嘗試獲取鎖對象,如果不能獲取或在指定時間內不能獲取,將立即返回。調用者可以根據返回值來判斷是否獲得了鎖對象以做進一步的操作。
3.7 Nested Locks 1. 在一個同步方法內調用同一個對象上的另外的同步方法的情況,稱之為嵌套鎖(nested locking)。系統將自動執行嵌套的同步方法,而無須等待鎖的釋放。因此,有的時候,即使一些私有方法僅僅被已同步的方法調用,我們也給其加上synchronized關鍵字,以減少后續維護時可能產生的誤導。
2. ReenterantLock類也支持嵌套鎖。在ReenterantLock類維持一個計數器,只有當這個計數器為0時,才會釋放鎖。注意:
這個特性是ReenterantLock類的特性,而不是所有實現Lock接口的類的特性。 3. 需要支持嵌套鎖的一個原因是方法之間交叉調用(cross-calling)。設想對象a的方法M1調用對象b的方法N1,然后N1再調用對象a的方法M2,而M1和M2都是同步方法,如果不支持嵌套鎖,則N1將在調用M2時等待M1釋放鎖,而M1則由于N1沒有返回永遠也不會釋放鎖,這樣就產生了死鎖。
4. synchronized和Lock接口并沒有提供鎖對象被嵌套獲取的次數,但ReentrantLock則提供了這樣一種機制:
public class ReentrantLock implements Lock {
public int getHoldCount();
public boolean isLocked();
public boolean isHeldByCurrentThread();
public int getQueueLength();
...
}
其中:
1) getHoldCount()返回當前線程的獲取次數,返回0并不表示該鎖是可獲取的,有可能沒有被當前線程獲得;
2) isLocked()判斷該鎖對象是否被任何線程獲得;
3) isHeldByCurrentThread()判斷是否由當前線程獲得;
4) getQueueLength()用來估計當前有多少線程在等待獲取這個鎖對象。
3.8 Deadlock 介紹了死鎖的概念,并修改例子代碼來演示deadlock的產生。死鎖一般產生在多個線程在多個鎖上同步時產生的;當然,多個線程在判斷多個條件的時候,也有可能產生死鎖。
3.9 Lock Fairness 1. Java里鎖的獲取機制依賴于底層多線程系統的實現,并不保證一個特定的順序;
2. ReentrantLock類提供一個先進先出(first-in-first-out)的獲取鎖的選項(在創建鎖對象時傳入true值)。

文章來源:
http://blog.csdn.net/evanwhj/archive/2006/03/05/616068.aspx