標籤:
Java 類庫中包括很多實用的”基礎模組“類。通常,我們應該優先選擇重用這些現有的類而不是建立新的類。:重用能減少開發工作量、開發風險(由於現有類都已經通過測試)以及維護成本。有時候,某個安全執行緒類能支援我們須要的全部操作,但很多其它的時候,現有的類僅僅能支援大部分的操作,此時就須要在不破壞安全執行緒的情況下加入一個新的操作。
如果我們須要一個安全執行緒的鏈表,他須要提供一個原子的”若沒有則加入(Put-If-Absent)“的操作。同步的 List 類已經實現了大部分的功能,我們能夠依據它提供的 contains 方法和 add 方法構造一個實現。
能夠有四種方法來實現這個原子操作。
第一種方法,也是最安全的方法,便是改動原始類。
但這一般是無法做到的,由於你可能無法訪問或改動類的源碼。要想改動原始的類,就須要深刻理解代碼中的同步策略,這樣添加的功能才幹與原有的設計保持一致。假設直接將新方法加入到類中,那麼意味著實現同步策略的全部代碼仍然處於一個源碼檔案裡,從而更easy理解與維護。
另外一種方法,能夠擴充(繼承)這個類—-假如原始類在設計的時候時考慮到了它的可擴充性。
比如,我們能夠設計一個 BetterVector 對 Vector 進行擴充,並加入了一個新方法 putIfAbsent。
public class BetterVector<E> extends Vector<E>{public synchronized boolean putIfAbsent(E x){boolean absent = !contains(x);if(absent)add(x);return absent;}}
擴充 Vector 非常easy,但並不是全部的類都像 Vector 那樣將狀態向子類公開,因此也就不適合採用這樣的方法。
”擴充“方法比較脆弱,主要原因是 同步策略的實現被分離到了多個源碼檔案裡,假設底層類改變了同步策略,更改了不同的鎖來保護狀態,那麼子類便會被破壞。
第三種方法,使用輔助類,實現client加鎖機制。
對於某些類,比方 Collections.synchronizedList 封裝的 ArrayList , 前兩種方法都行不通,由於客戶代碼不知道在同步封裝器Factory 方法中返回的 List 對象的類型。這時候採用client加鎖的方式,將擴充代碼放到一個”輔助類“中。
於是我們非常自然的就寫出 ListHelper 輔助類。
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 ,可是它卻是在 ListHelper 上加鎖,而 List 卻是用自己或內部對象的鎖。 ListHelper 僅僅是帶來了同步的假象,
在 Vector 和同步封裝器類的文檔中指出,他們是通過 Vector 或封裝容器內部鎖來支援client加鎖。以下我們給出正確的client加鎖。
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;}}}
通過加入一個原子操作來擴充類是脆弱的,由於它將類的加鎖代碼分布到多個類中。然而,client加鎖卻更加脆弱,由於它將類的加鎖代碼放到與其全然無關的其它類中。
第四種方法,使用組合(Composition)的方式。
public class ImprovedList<T> implements List<T> {public 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;}//...依照相似的方式託付List的其它方法}
ImprovedList 通過自身的內建鎖添加了一層額外的加鎖。它並不關心底層的 List 是否是安全執行緒的,即使 List 不是安全執行緒的或者改動了它的枷鎖方式,Improved 也會提供一致的加鎖機制來實現執行緒安全性。儘管額外的同步層可能導致輕微的效能損失,但與類比還有一個對象的加鎖策略相比,ImprovedList 更為健壯。其實,我們使用了 Java 監視器模式來封裝現有 List ,而且僅僅要在類中擁有指向底層 List 的為意外不引用,就能確保執行緒安全性。
Java 並發編程(三)為安全執行緒類中加入新的原子操作