concurrent包下的并發容器
JDK5中添加了新的concurrent包,相對同步容器而言,并發容器通過一些機制改進了并發性能。因為同步容器將所有對容器狀態的訪問都串行化了,這樣保證了線程的安全性,所以這種方法的代價就是嚴重降低了并發性,當多個線程競爭容器時,吞吐量嚴重降低。因此Java5開始針對多線程并發訪問設計,提供了并發性能較好的并發容器,引入了java.util.concurrent包。
與Vector、HashTable、Collections.synchronizedXxx()等同步容器相比,util.concurrent中引入的并發容器主要解決了兩個問題:
- 根據具體場景進行設計,盡量避免synchronized,提供并發性。
- 定義了一些并發安全的復合操作,并且保證并發環境下的迭代操作不會出錯。
util.concurrent中容器在迭代時,可以不封裝在synchronized中,可以保證不拋異常,但是未必每次看到的都是"最新的、當前的"數據。
下面是對并發容器的簡單介紹:
ConcurrentHashMap代替同步的Map(Collections.synchronized(new HashMap())),眾所周知,HashMap是根據散列值分段存儲的,同步Map在同步的時候鎖住了所有的段,而ConcurrentHashMap加鎖的時候根據散列值鎖住了散列值鎖對應的那段,因此提高了并發性能。ConcurrentHashMap也增加了對常用復合操作的支持,比如"若沒有則添加":putIfAbsent(),替換:replace()。這2個操作都是原子操作。
CopyOnWriteArrayList和CopyOnWriteArraySet分別代替List和Set,主要是在遍歷操作為主的情況下來代替同步的List和同步的Set,這也就是上面所述的思路:迭代過程要保證不出錯,除了加鎖,另外一種方法就是"克隆"容器對象。
ConcurrentLinkedQueue是一個先進先出的隊列。它是非阻塞隊列。
ConcurrentSkipListMap可以在高效并發中替代SortedMap(例如用Collections.synchronizedMap包裝的TreeMap)。
ConcurrentSkipListSet可以在高效并發中替代SortedSet(例如用Collections.synchronizedSet包裝的TreeMap)。
為什么使用ConcurrentHashMap
線程不安全的HashMap
并發編程中使用HashMap可能導致程序死循環,導致cpu利用率接近100%。具體原因是,再執行put操作時會引起死循環,多線程會導致HashMap的Entry鏈表形成環形數據結構,這樣的話Entry的next節點永遠不為空,就會產生死循環獲取Entry。
效率低下的HashTable
HashTable使用synchronized來保證線程安全,競爭激烈情況下,當一個線程訪問同步方法,其他線程也訪問同步方法,會進入阻塞或輪詢狀態。線程1使用put進行元素添加,線程2不但不能使用put方法,也不能使用get方法。
ConcurrentHashMap使用鎖分段技術
ConcurrentHashMap將數據分成一段一段地存儲,然后給每一段數據配一把鎖,并且其內部的結構可以讓其在進行寫操作的時候能夠將鎖的粒度保持盡量的小,不用對整個ConcurrentHashMap加鎖。當一個線程占用鎖訪問其中一個段數據的時候,其他段地數據也能被其他線程訪問。
ConcurrentHashMap的結構
ConcurrentHashMap是由Segment數組結構和HashEntry數組結構組成。Segment是一種可重入鎖ReentrantLock,在ConcurrentHashMap里扮演鎖的角色,HashEntry則用于存儲鍵值對數據。一個ConcurrentHashMap里包含一個Segment數組,Segment的結構和HashMap類似,是一種數組和鏈表結構, 一個Segment里包含一個HashEntry數組,每個HashEntry是一個鏈表結構的元素, 每個Segment守護著一個HashEntry數組里的元素,當對HashEntry數組的數據進行修改時,必須首先獲得它對應的Segment鎖。
ConcurrentHashMap和HashTable的區別圖
從上面的結構我們可以了解到,ConcurrentHashMap定位一個元素的過程需要進行兩次Hash操作,第一次Hash定位到Segment,第二次Hash定位到元素所在的鏈表的頭部,因此,這一種結構的帶來的副作用是Hash的過程要比普通的HashMap要長,但是帶來的好處是寫操作的時候可以只對元素所在的Segment進行加鎖即可,不會影響到其他的Segment,這樣,在最理想的情況下,ConcurrentHashMap可以最高同時支持Segment數量大小的寫操作(剛好這些寫操作都非常平均地分布在所有的Segment上),所以,通過這一種結構,ConcurrentHashMap的并發能力可以大大的提高。