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..
寫好文件,非常重要!
//待下篇