1. 程式人生 > >Java併發程式設計學習記錄#4

Java併發程式設計學習記錄#4

組合物件

探討一些構造類的模式,使得類更容易成為執行緒安全的。

設計執行緒安全的類

設計執行緒安全的類的過程應該包含三個方面:
- 確定物件狀態是由哪些變數構成–變數;
- 確定限制物件狀態的不變約束–不變約束;
- 制定一個管理併發訪問物件狀態的策略–後驗條件。

不變約束:用來判定一個狀態是合法的還是不合法的,比如int的取值範圍,是施加在狀態上的約束;

後驗條件:指出某種狀態轉變是否合法,是施加在狀態操作上的約束;

上述二者需要引入額外的同步和封裝。

組合:將一個物件封裝到另一個物件內部。

組合使得被封裝物件的全部訪問路徑都是可知的,這相比讓整個應用系統 訪問物件來說,更容易對訪問路徑進行分析,然後再和各種適當的鎖相結合,可以確保程式能以執行緒安全的方式使用其它非執行緒安全的物件。

在併發領域,組合是為保證執行緒安全的的一個執行緒限制。

Java監視器模式

一種執行緒限制原則,遵循該原則的物件封裝了所有的可變狀態,並使用物件的內部鎖來保護。例如:

public class PrivateLock {
    private final Object myLock = new Object();
    @GuardedBy("myLock") Widget widget;

    void someMethod() {
        synchronized (myLock) {
            // Access or modify the state of widget
} } }

委託執行緒安全

一些由執行緒安全的元件組合而成的元件未必是執行緒安全的。比如這些執行緒安全的子元件有依賴性。見程式碼:

public class NumberRange {
    // INVARIANT: lower <= upper
    private final AtomicInteger lower = new AtomicInteger(0);
    private final AtomicInteger upper = new AtomicInteger(0);

    public void setLower(int i) {
        // Warning -- unsafe check-then-act
if (i > upper.get()) throw new IllegalArgumentException("can't set lower to " + i + " > upper"); lower.set(i); } public void setUpper(int i) { // Warning -- unsafe check-then-act if (i < lower.get()) throw new IllegalArgumentException("can't set upper to " + i + " < lower"); upper.set(i); } public boolean isInRange(int i) { return (i >= lower.get() && i <= upper.get()); } }

若有兩個執行緒,同時,分別訪問setLower和setUpper,則可能出現執行緒安全問題。

只有當一個類有多個彼此獨立的 執行緒安全的狀態變數組合而成,且類的操作不包含任何無效的狀態轉換時,才可以將執行緒安全委託給這些狀態變數。

向已有的執行緒安全類中新增功能

重用Java自帶的執行緒安全類,要好於建立一個新的,無論在難度,風險或是維護上。比如對一個List新增功能:缺少則新增。即先判斷list中是否有此元素,無,則新增。此時涉及到了檢查-執行這一複合操作,按照之前同步策略,是可以在此操作上加鎖將其變成原子性操作的,但是因為原始碼我們沒法修改,只能找別的方式。這裡,組合,或是繼承都可以。比如繼承:

@ThreadSafe
public class BetterVector <E> extends Vector<E> {
    // When extending a serializable class, you should redefine serialVersionUID
    static final long serialVersionUID = -3963416950630760754L;

    public synchronized boolean putIfAbsent(E x) {
        boolean absent = !contains(x);
        if (absent)
            add(x);
        return absent;
    }
}

不過組合(這裡只指原功能的執行緒安全委託給子元件)的話,需要一些其它的操作,不能直接在方法上同步。因為Q1操作無法保證helper類封裝的list的其它方法和putif..的同步問題。

@NotThreadSafe//Q1操作,不安全
class BadListHelper <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;
    }
    ...
}

@ThreadSafe
class GoodListHelper <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;
        }
    }
    ...
}

還有另一種組合方式,就是將所組合的物件中所有存在風險的方法都加上內部鎖,而不用依賴子元件物件是否執行緒安全。

@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;
    }

    public synchronized boolean add(T e) {
        return list.add(e);
    }

    public synchronized boolean remove(Object o) {
        return list.remove(o);
    }
    .....other alike methods..

寫好文件,非常重要!

//待下篇

主要參考自_ Java Concurrency in Practice