1 同步容器類
同步容器類包括Vector和HashTable,二者是早期JDK一部分,此外還包括在JDK 1.2中添加的一些功能相似的類,這些的同步封裝器類是由Collections.synchronizedXxx等工廠方法建立的。這些類實作線程安全的方式是:将他們的狀态封裝起來,并對每個共有方法進行同步,使得每次隻有一個線程能通路容器的狀态。
1.1 同步容器類的問題
同步容器類都是線程安全的,但在某些情況可能需額外用戶端加鎖來保護複合操作。
容器上常見的複合操作包括:
- 疊代(反複通路元素,直到周遊完容器中所有元素)
- 跳轉(根據指定順序找到目前元素的下一個元素)以及條件運算
在同步容器類中,這些複合操作在沒有用戶端加鎖的情況下,仍是線程安全的,
但當其他線程并發的修改容器時,他們可能會表現出意料之外的行為。
2 并發容器
Java5提供了多種并發容器來改進同步容器的性能。
同步容器将所有對容器狀态的通路都串行化,以實作他們的線程安全性。
這種方法的代價是嚴重降低并發性,當多個線程競争容器的鎖時,吞吐量将嚴重降低。
并發容器是針對多個線程并發通路設計的。在Java 5中增加了
- ConcurrentHashMap,用來替代同步且基于散列的Map,增加了對一些常見符合操作的支援,例如“若沒有則添加”、替換以及有條件删除等。
- CopyOnWriteArrayList,用于在周遊操作為主要操作的情況下代替同步的List。
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIn5GcuMWOyAzMhFzYyYTNkVGMyQjN3AzY5MGN4gTO5Y2YmF2MfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.png)
copyOnWriteArrayList 和 copyOnWriteSet 一開始都共享同一個内容,當想要修改内容時,才會真正的把内容 copy 出去,形成一個新的内容後再改
比如:當我們往一個容器添加元素時,不直接往目前容器添加,而是先将容器進行 copy,複制出一個新容器,再往新容器裡加元素。添加完之後,再将原容器引用指向新容器。
好處:對 copyOnWrite 容器進行并發讀時,不需要加鎖,因為目前容器不會增加新元素,讀寫分離
copyOnWriteArrayList#add
要加鎖,否則多線程時會 copy N 個副本
copyOnWrite 适合于
讀多寫少
場景,但隻能保證資料最終一緻性,不保證明時一緻性
若你希望寫入馬上被讀到,不要用 copyOnWrite 容器
通過并發容器來代替同步容器,可以極大地提供伸縮性并降低風險。
2.1 CocurrentHashMap
同步容器在執行每個操作期間都持有一個鎖。在一些操作中,例如
HashMashMap.get
或
List.contains
,可能包含大量的工作:當周遊散列桶或連結清單來查找某個特定的對象時,必須在許多元素上調用equals。在基于散列的容器中,如果hashCode不能很均勻的分布散列值,那麼容器中的元素就不會均勻的分布在整個容器中。某些情況下,某個糟糕的散列函數還會把一個散清單變成線性連結清單。當周遊很長的連結清單并且在某些或者全部元素上調用equals方法時,會花費很長時間,而其他線程在這段時間内都不能通路容器。
ConcurrentHashMap使用一種粒度更細的稱為分段鎖的機制來實作更大程度的共享.
在這種機制中,任意數量的讀取線程可以并發的通路Map,執行讀操作的線程和執行寫操作的線程可以并發的通路Map,并且一定數量的寫線程可以并發的修改Map.
ConcurrentHashMap與其他并發容器一起增強了同步容器:疊代器不會抛出ConcurrentModificationException,是以疊代過程無需加鎖.
其疊代器具有"弱一緻性",而并非"及時失敗".可以容忍并發的修改,當建立疊代器時會周遊已有的元素,并可以(但不保證)在疊代器被構造後将修改操作反映給容器.
隻有當需要加鎖Map以進行獨占通路時,才應該放棄使用ConcurrentHashMap.
2.2 額外的原子Map操作
由于ConcurrentHashMap不能被加鎖來執行獨占通路,是以 無法使用用戶端加鎖來建立新的原子操作.
一些常見的複合操作,eg."若沒有則添加","若相等則移除"等,都已經實作為原子操作并且在ConcurrentMap接口中聲明,如下面代碼所示.
public interface ConcurrentMap<K, V> extends Map<K, V> {
//僅當K沒有相應的映射值時才插入
V putIfAbsent(K key, V value);
//僅當K被映射到V時才移除
boolean remove(Object key, Object value);
//僅當K被映射到oldValue時才替換為newValue
boolean replace(K key, V oldValue, V newValue);
//僅當K被映射到某個值時才被替換為newValue
V replace(K key, V value);
}