對象的組合(第四章)
對象的組合
在設計線程安全類的過程中,需要包含以下三個基本要素:
1. 找出構成對象狀態的所有變量
2. 找出約束狀態變量的不變性條件
3. 建立對象狀態的並發訪問管理策略
- 當對象的下一個狀態需要依賴當前狀態時,這個操作就必須是一個複合操作。如有一個Counter類,當前狀態為17,那麼下一個狀態隻能是18
- 相關變量的讀取和更新必須在單個原子操作中進行
- 如果某個操作中包含有基於狀態的先驗條件,那麼這個操作就稱為依賴狀態的操作。如刪除一個隊列裏的元素,這個隊列當前必須是“非空”的
- 封閉機製更易於構造線程安全的類
1.Java監視器模式
遵循Java監視器模式的對象會把對象的所有可變狀態都封裝起來,並由對象自己的內置鎖來保護
以下代碼是對象使用私有鎖的例子:
public class PrivateLock {
private final Object myLock = new Object();
public void doSomething() {
synchronized(myLock) {...}
}
}
2.線程安全性的委托
委托是創建線程安全類的一個有效手段:隻需讓現有的線程安全類管理所有的狀態即可。
通過多個線程安全類組合而成的類,其線程安全性視情況而定。
1. 對象中有單個線程安全對象是線程安全的
2. 如果一個類是由多個獨立且線程安全的狀態變量組成的,並且在所有的操作中都不包含無效狀態轉換,那麼可以將線程安全性委托給底層的狀態變量
3. 如果類中多個變量之間存在著一定的不變性條件,那麼這個類就不是線程安全的,需要采取同步機製
4. 如果一個狀態變量是線程安全的,並且沒有任何不變性條件來約束它的值,在變量的操作上也不存在不允許的狀態轉換,那麼就可以安全地發布這個變量
3.在現有的線程安全類中添加功能
要在現有的類中添加線程安全操作,有兩種方法:
1. 修改原始類的代碼
2. 擴展這個類。一下代碼擴展了Vector
public class BetterVector<E> extends Vector<E> {
public synchronized boolean putIfAbsent(E x) {
boolean absent = !contains(x);
if (absent) {add(x);}
return absent;
}
}
擴展的方法比直接修改源代碼要脆弱,因為同步策略實現被分布到了多個單獨維護的源代碼文件中。如果底層的類改變了同步策略並選擇了不同的鎖來保護它的狀態變量,那麼子類會被破壞。
3. 客戶端加鎖機製
客戶端加鎖機製是指,對於使用某個對象X的客戶端代碼,使用X本身用於保護其狀態的鎖來保護這段客戶代碼。要使用客戶端加鎖機製,必須知道對象X使用的是哪一個鎖。如下代碼中,第一個實現版本使用了不同的鎖,所以其並不是原子的。
public class ListHelper<E> {
public List<E> list = Collections.synchronizedList(new ArrayList<E>());
//verson 1
public synchronized boolean putIfAbsent(E x) {
boolean absent = !list.contains(x);
if (absent) list.add(x);
return absent;
}
//verson 2
public boolean putIfAbsent(E x) {
synchronized(list) {//使用list自己的鎖
boolean absent = !list.contains(x);
if (absent) list.add(x);
return absent;
}
}
}
- 組合 為現有類添加一個原子操作時,更好的方法是:組合(現有類作為對象的私有final域)
4.將同步策略文檔化
在文檔中說明客戶代碼需要了解的線程安全性保證,以及代碼維護人員需要了解的同步策略
最後更新:2017-11-04 18:03:41