設計線程安全的類
- 在設計線程安全類的過程中需要包含以下三個基本要素:
- 找出構成對象狀態的所有變量
- 找出約束狀態變量的不變性條件
- 建立對象狀態的併發訪問管理策略
- 同步策略規定了如何將不變性、線程封閉與加鎖機制等結合起來以維護線程的安全性,並且還規定了哪些變量由哪些鎖來保護。
- 如果在一個不變性條件中包含多個變量,那麼在執行任何訪問相關變量的操作時,都必須持有保護這些變量的鎖。
實例封閉
- 將數據封裝在對象內部,可以將數據的訪問限制在對象的方法上,從而更容易確保線程在訪問數據時總能持有正確的鎖。
- 實例封閉使得不同的狀態變量可以由不同的鎖來保護。
- 包裝器工廠方法(Collections.SynchronizedList)通過包裝器將容器類封裝在一個同步的包裝器對象中,而包裝器能夠將接口中的每個方法都實現爲同步方法,並將調用請求轉發到底層的容器對象上。只要包裝器對象擁有對底層容器對象的唯一引用,那麼它就是線程安全的。
- 監視器模式:遵循Java監視器模式的對象會把對象的所有可變狀態都封裝起來,並由對象自己的內置鎖來保護。對於任何一種鎖對象,只要自始至終都使用該鎖對象,都可以用來保護對象的狀態。
線程安全線的委託
- 如果一個類是由多個獨立且線程安全的狀態變量組成,並且在所有的操作中都不包含無效狀態轉換,那麼可以將線程安全性委託給底層的狀態變量。
- CopyOnWriteArrayList是一個線程安全的鏈表,特別適用於管理監聽器列表。
- 如果一個狀態變量是線程安全的,並且沒有任何不變性條件來約束它的值,在變量的操作上也不存在任何不允許的狀態轉換(不需要對變量的有效值進行判斷),那麼就可以安全地發佈這個變量。
在現有的線程安全類中添加功能
- 擴展方法比直接將代碼添加到類中更加脆弱。
- 客戶端加鎖是指對於使用某個對象X的客戶端代碼,使用X本身用於保護其狀態的鎖來保護這段客戶代碼。
- 組合:使用Java監視器模式封裝現有的list,只要外部類中擁有指向底層list的唯一外部引用,就能確保線程安全線。
public class ImprovedList<T> implements List<T> {
private final List<T> list;
public ImprovedList(List<T> list) {
this.list = list;
}
public synchronized boolean puIfAbsent(T x) {
boolean contains = list.contains(x);
if (!contains)
list.add(x);
return !contains;
}
public synchronized void clear() {
list.clear();
}
}
將同步策略文檔化
- 在文檔中說明客戶代碼需要了解的線程安全線保證,以及代碼維護人員需要了解的同步策略。
- 設計階段是編寫設計決策文檔的最佳時間。
- 如果某個類沒有明確地聲明是線程安全的,那麼就不要假設它是線程安全的。