在JDK1.5中引入了java.util.concurrent包,在該包中定義了一組線程安全的集合,稱為并發集合, 這些集合可以作為同步集合的替代品。
非線程安全的集合 | 并發集合 | 共同接口 |
---|---|---|
ArrayList | CopyOnWriteArrayList | List |
LinkedList | ConcurrentLinkedQueue | Queue |
HashSet | CopyOnWriteArraySet | Set |
TreeSet | ConcurrentSkipListSet | SortedSet |
HashMap | ConcurrentHashMap | Map |
TreeMap | ConcurrentSkipListMap | SortedMap |
并發集合實現線程安全的遍歷方式有兩種:
1、一個是對待遍歷集合的快照進行遍歷. 快照(Snapshot)是指在創建iterator迭代器對象時給集合內部的數據結構創建一個副本,它反映的是在迭代這一時刻的狀態,每個線程在迭代遍歷集合時,都會創建本線程的一個副本,就相當于這個快照是線程的特有對象,所以在遍歷操作時無須加鎖. 另外快照是只讀的,因此返回的Iterator迭代器不支持remove()刪除操作. 這種快照遍歷方式優點是遍歷操作與更新操作互不影響,缺點是集合元素非常多時,創建快照開銷比較大. CopyOnWriterArrayList與CopyOnWriteArraySet這兩個集合采用了快照遍歷方式。
2、另一種遍歷是準實時遍歷,準實際遍歷不是針對副本遍歷,也不使用鎖來保障線程安全,遍歷操作與更新可以并發進行,這種遍歷方式支持迭代器的remove()操作的, 你刪除后可能會在其他線程遍歷時立即就反映出來ConcurrentLinkedQueue和ConcurrentHashMap等并發集合采用了這種準實時遍歷方式。
并發集合內部在保障線程安全的時候不使用鎖,采用CAS操作,或者對鎖進行優化,如使用粒度極小的鎖.相對于同步集合,使用并發集合的程序的吞吐率提升非常明顯,同步集合的程序隨著并發數量的增長,會使得集合內部所使用鎖的爭用所導致的線程上下文切換加劇。
ArrayList集合是使用比較頻繁的一個集合, 它底層數據結構是數組,底層是通過數組來存儲集合中的元素,它不是線程安全的. 開發多線程程序,可以使用同步集合Vector,或者調用Collections.synchronizedList()把不是線程安全的ArrayList轉換為線程安全的。
在很多應用場合下,讀操作可能遠遠大于寫操作,希望讀操作可以盡可能的快,寫操作慢一些也沒有關系.讀操作不會修改原來的數據,因此每次在讀操作進行加鎖是一種資源浪費,在同步集合Vector與Collections.synchronizedList()返回的線程安全集合中,每次在讀數據時都會進行加鎖同步,它們讀取數據的效率就低. 根據讀寫鎖思想,讀鎖與讀鎖不沖突,讀操作會受到寫操作的阻礙,在寫操作時,讀操作必須進行等待,如果在讀操作時,寫操作也需要等待。
為了將讀取的性能發揮到極致,在JDK5中引用了CopyOnWriteArrayList集合, 該集合在讀取數據時完全不用加鎖,并且寫操作也不會阻塞讀操作. 從集合類名來看CopyOnWrite就是在寫入操作時,進行一次自我復制.即當向集合寫入數據并不修改原有的內容, 而是把集合中原來的從容復制到一個副本中,向副本中寫入數據,寫完后再將副本替換原來的數據。
CopyOnWriteArrayList集合采用快照遍歷,在迭代時,不支持刪除操作。
ConcurrentLinkedQueue類可以看作是LinkedList類的線程安全版, 可以作為Collections.sychronizedList(LinkedList)替代品. ConcurrentLinkedQueue內部訪問共享狀態變量(隊首與隊尾指針)時并不使用鎖,而是使用CAS操作來保障線程安全的. ConcurrentLinkedQueue是非阻塞的,避免了上下文切換需要的開銷.遍歷方式是準實時. 與BlockingQueue阻塞隊列比,ConcurrentLinkedQueue更適合更新操作與遍歷操作并發的情況, 有若干的線程往/從隊列中添加/刪除操作,還有若干的線程讀取集合中的數據。
ConcurrentLinkedQueue是一個高效的讀寫隊列,在多線程中可以使用BlockingQueue阻塞隊列在線程之間共享數據。
BlockingQueue是一個接口,主要有兩個實現類ArrayBlockingQueue與LinkedBlockingQueue. ArrayBlockingQueue是基于數組實現的,更適合做有界隊列,在隊列中存儲元素的容量可以在創建隊列時指定; LinkedBlockingQueue基于鏈表實現的,適合做無界隊列,因為內部元素可以動態的增加。
BlockingQueue阻塞隊列有兩個常用的操作:put()與take(). put()方法是將元素添加到隊列的尾部,如果隊列滿了,它會一直等待,直到隊列中有空閑的位置; take()會從隊列的頭部取出一個元素,如果隊列為空會一直等待,等到隊列中有可用元素再取。
HashTable是一個同步集合,在某個線程操作HashTable期間不允許其他線程參與。
Collections.synchronizedMap(Map)可以返回一個線程安全的集合,采用了裝飾器模式,在該線程安全的集合內部,先要獲得mutex鎖,并發效率低。
ConcurrentHashMap是一種高并發的線程安全的Map集合,可以看作是HashTable的替代品. 在JDK7前,ConcurrentHashMap內部使用粒度極小的鎖來保障線程安全,或者說采用了分段鎖協議,默認情況下可以支持16個線程并發操作. 在JDK8中對ConcurrentHashMap集合進行了性能提升,采用CAS操作實現線程安全。
跳表是一種可以用來快速查詢的數據結構.與平衡樹相比,在對跳表插入/刪除操作時,只需要對整數數據結構的局部進行操作即可,即在高并發的情況下, 你可能需要一個全局的鎖來保障整個平衡樹的安全,對于跳表來說,只需要部分鎖即可。
使用這一數據結構的集合有: ConcurrentSkipListSet與ConcurrentSkipListMap集合, ConcurrentSkipListSet集合是可以對元素進行排序的線程安全的集合, ConcurrentSkipListMap集合是可以根據鍵進行排序的線程安全的Map集合。