java併發編程實戰閱讀筆記(第四章)對象的組合

一、設計線程安全的類

在設計線程安全類的過程中,需要包含三個步驟:
1)找出構成對象狀態的所有變量。
2)找出約束狀態變量的不變形條件。
3)建立對象狀態的併發訪問管理策略。

對象的域:是指對象中的變量。
對象的狀態:如果對象中的所有域都是基本類型的變量,那麼這些域將構成對象的全部狀態。如果對象中引用了其他對象,那麼該對象的狀態將包含被引用對象的域。

收集同步需求

如果不瞭解對象的不變性條件與後驗條件,那麼就不能確保線程安全性。要滿足在狀態變量的有效值或狀態轉換上的各種約束條件,就需要藉助原子性和封裝性。
說的更簡略些是Java線程安全問題都是因爲共享變量,共享變量後會因爲多個線程同時修改導致不正確的問題,所以收集一共有多少處會涉及到這些需要同步的變量,只有收集說有可能出問題的因素基於此之上保證所有元素線程安全也才能保證程序是線程安全的。

依賴狀態的操作

在某些對象的方法中還包含一些基於狀態的先驗條件,如刪除前的非空判斷,這樣的操作就被稱爲依賴狀態的操作。要想實現某個等待先驗條件爲真時才執行的操作,一種簡單的方法是通過現有庫中的類,如阻塞隊列Blocking Queue或者信號量Semaphore來實現依賴狀態的行爲。

狀態的所有權

單獨一個基本對象比較保證其安全性,但是如果是包含對象的集合(容器類 例如:ArrayList),容器類通常表現出一種“所有權分離”的形式。
即使用線程安全的容器類(Collections.synchronizedList(List)),也只能保證容器相關的操作是線程安全的,如果發佈了可變對象的引用,就不會擁有獨佔的控制權。(非線程安全)

二、實例封閉

將數據封裝在對象內部,可以將數據的訪問限制在對象的方法上,從而更容易確保線程在訪問數據時總能持有正確的鎖。
封閉機制更易於構造線程安全的類,因爲當封閉類的狀態時,在分析類的線程安全性時就無須檢查整個程序。
Java監聽器模式
遵循java監視器模式的對象會把對象的所有可變狀態都封裝起來,並由對象自己的內置鎖來保護。
示例:

public class Counter {
    private long value = 0;

    public synchronized long getValue(){
        return this.value;
    }

    public synchronized long increment(){
        if(value == Long.MAX_VALUE){
            throw new IllegalStateException("counter overflow");
        }
        return ++value;
    }
}

或者:

public class Counter {

    private Long value = 0l;

    public long getValue(){
        synchronized (value) {
            return this.value;
        }
    }

    public long increment(){
        synchronized (value) {
            if(value == Long.MAX_VALUE){
                throw new IllegalStateException("counter overflow");
            }
            return ++value;
        }
    }
}

使用私有的鎖對象而不是對象的內置鎖,有許多優點,私有的鎖對象可以將鎖封裝起來,使客戶端代碼無法獲取到鎖,以便參與到它的同步策略中,避免產生活躍性問題。

synchronized爲什麼不能修飾基本數據類型?
因爲基本數據類型是放在棧裏面的,棧數據是可共享的,所以不能加synchronized。

三、線程安全性的委託

通過多個線程安全的類組成的類不一定是線程安全的
1、委託給單個線程安全狀態變量可以保證線程安全性
2、委託給多個相互獨立的線程安全狀態變量可以保證線程安全性(完全獨立,不存在依賴狀態的操作)
3、如果類包含多個線程安全狀態變量的符合操作,則無法保證線程安全性,可以通過加鎖機制保證
4、安全發佈底層狀態的線程安全類
concurrentHashMap
copyOnWriteArrayList

四、現有的線程安全類中添加功能

1、客戶端加鎖機制
  使用某個對象的代碼時必須使用該對象本身用於保護其狀態的鎖,不推薦(同步的實現被分到兩個不相關的類中)
在客戶端加鎖,但要確保使加鎖對象在實現客戶端加鎖或者外部加鎖時使用同一個鎖。

@ThreadSafe  
public class ListHelper<E>{  
    public List<E> list = Collections.synchronizedList(new ArrayList<E>());  
    public boolean putIfAbsent(E x){  
        synchronized(list){//使List在實現客戶端加鎖或者外部加鎖時使用同一個鎖  
            boolean absent = !list.contains(x);  
            if(absent)  
                list.add(x);  
            return absent;  
        }  
    }  
}  

另外一個更好的方式是使用組合

@ThreadSafe  
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 contains = list.contains(x);  
        if(!contains)  
            list.add(x);  
        return !contains;  
    }  
}  

將同步策略文檔化

在文檔中說明客戶代碼需要了解的線程安全性保證,以及代碼維護人員需要了解的同步策略。

發佈了55 篇原創文章 · 獲贊 8 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章