物件的組合(第四章)

關山月朦朧發表於2017-11-04

物件的組合

    在設計執行緒安全類的過程中,需要包含以下三個基本要素:
    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;
    }
}

擴充套件的方法比直接修改原始碼要脆弱,因為同步策略實現被分佈到了多個單獨維護的原始碼檔案中。如果底層的類改變了同步策略並選擇了不同的鎖來保護它的狀態變數,那麼子類會被破壞。

  1. 客戶端加鎖機制
    客戶端加鎖機制是指,對於使用某個物件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;
        }
    }
}
  1. 組合
    為現有類新增一個原子操作時,更好的方法是:組合(現有類作為物件的私有final域)

4.將同步策略文件化

在文件中說明客戶程式碼需要了解的執行緒安全性保證,以及程式碼維護人員需要了解的同步策略


相關文章