併發實戰 之「 基礎構建模組」
委託是建立執行緒安全類的一個最有效的策略:只需讓現有的執行緒安全類管理所有的狀態即可。在本篇博文中,主要介紹一些比較有用的併發構建模組,特別是在 Java 5.0 和 Java 6.0 中引入的一些新模組,以及在使用這些模組來構造應用程式時的一些常用模式。
同步容器類
最早出現的同步容器類是Vector
和Hashtable
,在 JDK 1.2 及之後,又提供了一些功能類似的封裝器類,這些同步容器類是由Collections.synchronizedXxx
等工廠方法建立的,其實現執行緒安全的方式是:將它們的狀態封裝起來,並對每個公有方法都進行同步,使得每次只有一個執行緒能訪問容器的狀態。
同步容器類都是執行緒安全的,但在某些情況下可能需要額外的客戶端加鎖來保護符合操作。容器上常見的複合操作包括:迭代、跳轉以及條件運算。在同步容器類中,這些複合操作在沒有客戶端加鎖的情況下是執行緒安全的,但是在其他執行緒併發的修改容器時,它們可能會表現出意料之外的行為。
for (int i = 0; i < vector.size(); i++) {
doSometing(vector.get(i));
}
如上述程式碼所示,在呼叫size()
和相應get()
方法之之間,Vector
的長度可能會發生變化,這種風險在對Vector
中的元素進行迭代時就可能出現。因此,上述的程式碼可能在執行時丟擲ArrayIndexOutOfBoundsException
synchronized(vector) {
for (int i = 0; i < vector.size(); i++) {
doSometing(vector.get(i));
}
}
對於這種複合操作存在併發風險的問題,不僅出現在Vector
之中,在 JDK 5.0 引入的for-each
迴圈語法中也存在類似的問題。雖然加鎖可以防止迭代器丟擲ConcurrentModificationException
,但是我們必須記住在所有對共享容器進行迭代的地方都需要加鎖。實際情況要更加複雜,因為在某些情況下,迭代器會隱藏起來,例如:
public class HiddenIterate {
private final Set<Integer> set = new HashSet<Integer>();
public synchronized void add(Integer i) {
set.add(i);
}
public synchronized void remove(Integer i) {
set.remove(i);
}
public void addTenThings() {
Random random = new Random();
for (int i = 0; i < 10; i++) {
add(random.nextInt());
}
System.out.println("DEBUG: added ten elements to " + set);
}
}
在上述程式碼中,addTenThings()
方法可能會丟擲ConcurrentModificationException
異常,因為在生成除錯資訊的過程中,toString()
方法會對容器進行迭代。在使用println
中的set
之前必須首先獲取HiddenIterate
的鎖,但是在除錯程式碼和日誌程式碼中通常會忽視這個要求。容器的hashCode()
和equal()
等方法也會間接地執行迭代操作,當容器作為另一個容器的元素或鍵值時,就會出現這種情況。通常,containsAll()
、removeAll()
和retainAll()
等方法,以及把容器作為引數的建構函式,都會對容器進行迭代。
———— ☆☆☆ —— 返回 -> 那些年,關於 Java 的那些事兒 <- 目錄 —— ☆☆☆ ————