Java類庫包含許多有用的「基礎模塊」類。一般,咱們應該優先選擇重用這些現有的類而不是建立新的類:重用能下降開發工做量、開發風險(由於現有的類都已經經過測試)以及維護成本。有時候,某個現有的線程安全類能支持咱們須要的全部操做,但更多時候,現有的類智能支持大部分的操做,此時就須要在不破壞線程安全性的狀況下添加一個新的操做。java
例如,假設須要一個線程安全的鏈表,它須要提供一個原子的「若沒有則添加(putIfAbsent)」的操做。同步的List類已經實現了大部分的功能,咱們能夠根據它提供的contains方法和add方法來構造一個「若沒有則添加」。因爲這個類必須是線程安全的,所以就隱含的增長了另外一個需求,即「若沒有則添加」這個操做必須是原子操做。這意味着若是在鏈表中沒有包含對象X,那麼在執行兩次「若沒有則添加」X後,在容器中只能包含一個X對象。然而,若是「若沒有則添加」操做不是也uanzi操做,那麼在某些執行狀況下,有兩個線程都將看到X不在容器中,而且都執行了添加X的操做,從而使容器包含兩個相同的對象。安全
要添加一個新的原子操做,最安全的方法是修改原始的類,但這一般沒法作到,由於你可能沒法訪問或修改類的源代碼。要想修改原始的類,就須要理解代碼中的同步策略,這樣增長的功能才能與原有的設計保持一致。若是直接將新方法添加到類中,那麼意味着實現同步策略的全部代碼任然處於一個源代碼文件中,從而更容易理解與維護。併發
另外一種方法是擴展這個類(假定在設計這個類時考慮了可擴展性,即類不是final修飾的)。下面的代碼中的BetterVector對Vector類進行了擴展,並添加了一個新方法putIfAbsent。擴展Vector很簡單,但並不是全部的類都像Vector那樣將狀態向子類公開,所以也就不適合採用這種方法。函數
public class BetterVector<E> extends Vector<E> { public synchronized boolean putIfAbsent(E x) { boolean absent = !contains(x); if (absent) { add(x); } return absent; } }
「擴展」方法比直接將代碼添加到類中更加脆弱,由於如今的同步策略實現被分不到多個單獨維護的源代碼文件中。若是底層的類改變了同步策略並選擇了不一樣的鎖來保護它的狀態變量,那麼子類會被破壞,由於在同步策略改變後它沒法再使用正確的鎖來控制對基類狀態的併發訪問。(在Vector的規範中定義了它的同步策略,所以BetterVector不存在這個問題)性能
對於由Collections.synchronizedList封裝的ArrayList,上述兩種方法都行不通,由於客戶端代碼並不知道在同步封裝器工廠中返回的List對象的類型。第三種策略是擴展類的功能,但並非擴展類自己,而是將擴展代碼放入一個「輔助類」中。下面的代碼實現了一個包含「若沒有則添加」操做的輔助類,用於對線程安全的List進行操做看,但其中的代碼是錯誤的:測試
public class ListHelper<E> { public List<E> list = Collections.synchronizedList(new ArrayList<E>()); public synchronized boolean putIfAbsent(E x) { boolean absent = !list.contains(x); if (absent) { list.add(x); } return absent; } }
爲何這種方式不能實現線程安全性?畢竟putIfAbsent已經聲明爲synchronized類型的方法,對不對?問題在於在錯誤的鎖上進行了同步。不管List使用哪個鎖來保護它本身的狀態,能夠肯定的是,這個鎖並非ListHelper上的鎖。ListHelper只是帶來了同步的假象,儘管全部的鏈表操做都被聲明爲synchronized,但卻使用了不一樣的鎖,這意味着putIfAbsent相對於List的其餘操做來講並非原子的,所以就沒法確保當putIfAbsent執行時,另外一個線程不會修改鏈表。this
要想使這個方法能正確執行,必須使List在客戶端實現加鎖或外部加鎖時使用的是同一個鎖。客戶端加鎖是指:對於使用某個對象X的客戶端代碼,使用X自己用於保護其狀態的鎖來保護這段客戶代碼。要使用客戶端加鎖,你必須知道對象X使用的是哪個鎖。spa
在Vector和同步封裝器類(Collections.synchronized[List/Map/Set])的文檔中指出,它們經過使用Vector或封裝容器內置鎖來支持客戶端加鎖。線程
public class ListHelper<E> { public List<E> list = Collections.synchronizedList(new ArrayList<E>()); public boolean putIfAbsent(E x) { synchronized(list) { boolean absent = !list.contains(x); if (absent) { list.add(x); } return absent; } } }
經過添加一個原子操做來擴展類是脆弱的,由於它將類的加鎖代碼分佈到多個類中。然而,客戶端加鎖卻更加脆弱,由於它將類C的加鎖代碼放到與C徹底無關的其餘類中。當在那些並不承諾遵循加鎖策略的類上使用客戶端加鎖時,要特別當心。設計
客戶端加鎖機制與擴展類機制有許多共同點,兩者都是將派生類的行爲與積累的實現耦合在一塊兒。和擴展會破壞實現的封裝性同樣,客戶端加鎖一樣會破壞同步策略的封裝性。
當爲現有的類添加一個原子操做時,還有一種更好的方法:組合(Composition)。下面的程序中的ImprovedList經過將List對象的操做委託給底層List實力來實現,同時還添加了一個原子的putIfAbsent方法(與Collections.synchronizedList和其餘容器封裝器同樣,ImprovedList假設把某個鏈表對象傳給構造函數之後,客戶代碼不會再直接使用這個對象,而只能經過ImprovedList來訪問它)。
public class ImprovedList<T> implements List<T> { private final List<T> list; public ImprovedList(List<T> list) { this.list = list; } public synchronized boolean putIfAbsent(T x) { boolean absent = !list.contains(x); if (absent) { list.add(x); } return absent; } //...按照相似的方式將ImprovedList的其餘方法實現委託給List }
ImprovedList經過自身的內置鎖增長了一層額外的鎖。它並不關心底層的List是不是線程安全的,及時List不是線程安全的或者在其後續版本中修改了它的加鎖實現,ImprovedList也會提供一直的加鎖機制來實現線程安全性。雖然額外的同步層可能致使輕微的性能損失,但與模擬另外一個對象的加鎖策略相比,ImprovedList更爲健壯。