1. 程式人生 > >併發實戰 之「 基礎構建模組」

併發實戰 之「 基礎構建模組」

委託是建立執行緒安全類的一個最有效的策略:只需讓現有的執行緒安全類管理所有的狀態即可。在本篇博文中,主要介紹一些比較有用的併發構建模組,特別是在 Java 5.0 和 Java 6.0 中引入的一些新模組,以及在使用這些模組來構造應用程式時的一些常用模式。

同步容器類

最早出現的同步容器類是VectorHashtable,在 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 的那些事兒 <- 目錄 —— ☆☆☆ ————