這是java高並發系列第26篇文章。
環境:jdk1.8。
本文內容
- 了解JUC常見集合,學會使用
- ConcurrentHashMap
- ConcurrentSkipListMap
- ConcurrentSkipListSet
- CopyOnWriteArraySet
- 介紹Queue接口
- ConcurrentLinkedQueue
- CopyOnWriteArrayList
- 介紹Deque接口
- ConcurrentLinkedDeque
JUC集合框架圖
圖可以看到,JUC的集合框架也是從Map、List、Set、Queue、Collection等超級接口中繼承而來的。所以,大概可以知道JUC下的集合包含了一一些基本操作,並且變得線程安全。
Map
ConcurrentHashMap
功能和HashMap基本一致,內部使用紅黑樹實現的。
特性:
- 迭代結果和存入順序不一致
- key和value都不能為空
- 線程安全的
ConcurrentSkipListMap
內部使用跳表實現的,放入的元素會進行排序,排序算法支持2種方式來指定:
- 通過構造方法傳入一個
Comparator
- 放入的元素實現
Comparable
接口
上面2種方式必選一個,如果2種都有,走規則1。
特性:
- 迭代結果和存入順序不一致
- 放入的元素會排序
- key和value都不能為空
- 線程安全的
List
CopyOnWriteArrayList
實現List的接口的,一般我們使用ArrayList、LinkedList、Vector
,其中只有Vector是線程安全的,可以使用Collections靜態類的synchronizedList方法對ArrayList、LinkedList包裝為線程安全的List,不過這些方式在保證線程安全的情況下性能都不高。
CopyOnWriteArrayList是線程安全的List,內部使用數組存儲數據,集合中多線程並行操作一般存在4種情況:讀讀、讀寫、寫寫、寫讀,這個只有在寫寫操作過程中會導致其他線程阻塞,其他3種情況均不會阻塞
,所以讀取的效率非常高。
可以看一下這個類的名稱:CopyOnWrite,意思是在寫入操作的時候,進行一次自我復制,換句話說,當這個List需要修改時,並不修改原有內容(這對於保證當前在讀線程的數據一致性非常重要),而是在原有存放數據的數組上產生一個副本,在副本上修改數據,修改完畢之后,用副本替換原來的數組,這樣也保證了寫操作不會影響讀。
特性:
- 迭代結果和存入順序一致
- 元素不重復
- 元素可以為空
- 線程安全的
- 讀讀、讀寫、寫讀3種情況不會阻塞;寫寫會阻塞
- 無界的
Set
ConcurrentSkipListSet
有序的Set,內部基於ConcurrentSkipListMap實現的,放入的元素會進行排序,排序算法支持2種方式來指定:
- 通過構造方法傳入一個
Comparator
- 放入的元素實現
Comparable
接口
上面2種方式需要實現一個,如果2種都有,走規則1
特性:
- 迭代結果和存入順序不一致
- 放入的元素會排序
- 元素不重復
- 元素不能為空
- 線程安全的
- 無界的
CopyOnWriteArraySet
內部使用CopyOnWriteArrayList實現的,將所有的操作都會轉發給CopyOnWriteArrayList。
特性:
- 迭代結果和存入順序不一致
- 元素不重復
- 元素可以為空
- 線程安全的
- 讀讀、讀寫、寫讀 不會阻塞;寫寫會阻塞
- 無界的
Queue
Queue接口中的方法,我們再回顧一下:
操作類型 | 拋出異常 | 返回特殊值 |
---|---|---|
插入 | add(e) |
offer(e) |
移除 | remove() |
poll() |
檢查 | element() |
peek() |
3種操作,每種操作有2個方法,不同點是隊列為空或者滿載時,調用方法是拋出異常還是返回特殊值,大家按照表格中的多看幾遍,加深記憶。
ConcurrentLinkedQueue
高效並發隊列,內部使用鏈表實現的。
特性:
- 線程安全的
- 迭代結果和存入順序一致
- 元素可以重復
- 元素不能為空
- 線程安全的
- 無界隊列
Deque
先介紹一下Deque接口,雙向隊列(Deque)是Queue的一個子接口,雙向隊列是指該隊列兩端的元素既能入隊(offer)也能出隊(poll),如果將Deque限制為只能從一端入隊和出隊,則可實現棧的數據結構。對於棧而言,有入棧(push)和出棧(pop),遵循先進后出原則。
一個線性 collection,支持在兩端插入和移除元素。名稱 deque 是“double ended queue(雙端隊列)”的縮寫,通常讀為“deck”。大多數 Deque
實現對於它們能夠包含的元素數沒有固定限制,但此接口既支持有容量限制的雙端隊列,也支持沒有固定大小限制的雙端隊列。
此接口定義在雙端隊列兩端訪問元素的方法。提供插入、移除和檢查元素的方法。每種方法都存在兩種形式:一種形式在操作失敗時拋出異常,另一種形式返回一個特殊值(null
或 false
,具體取決於操作)。插入操作的后一種形式是專為使用有容量限制的 Deque
實現設計的;在大多數實現中,插入操作不能失敗。
下表總結了上述 12 種方法:
第一個元素(頭部) | 第一個元素(頭部) | 最后一個元素(尾部) | 最后一個元素(尾部) | |
---|---|---|---|---|
拋出異常 | 特殊值 | 拋出異常 | 特殊值 | |
插入 | addFirst(e) |
offerFirst(e) |
addLast(e) |
offerLast(e) |
移除 | removeFirst() |
pollFirst() |
removeLast() |
pollLast() |
檢查 | getFirst() |
peekFirst() |
getLast() |
peekLast() |
此接口擴展了 Queue
接口。在將雙端隊列用作隊列時,將得到 FIFO(先進先出)行為。將元素添加到雙端隊列的末尾,從雙端隊列的開頭移除元素。從 Queue
接口繼承的方法完全等效於 Deque
方法,如下表所示:
此接口擴展了 Queue
接口。在將雙端隊列用作隊列時,將得到 FIFO(先進先出)行為。將元素添加到雙端隊列的末尾,從雙端隊列的開頭移除元素。從 Queue
接口繼承的方法完全等效於 Deque
方法,如下表所示:
Queue 方法 | 等效 Deque 方法 |
---|---|
add(e) |
addLast(e) |
offer(e) |
offerLast(e) |
remove() |
removeFirst() |
poll() |
pollFirst() |
element() |
getFirst() |
peek() |
peekFirst() |
ConcurrentLinkedDeque
實現了Deque接口,內部使用鏈表實現的高效的並發雙端隊列。
特性:
- 線程安全的
- 迭代結果和存入順序一致
- 元素可以重復
- 元素不能為空
- 線程安全的
- 無界隊列
BlockingQueue
關於阻塞隊列,上一篇有詳細介紹,可以看看:掌握JUC中的阻塞隊列
java高並發系列目錄
- 第1天:必須知道的幾個概念
- 第2天:並發級別
- 第3天:有關並行的兩個重要定律
- 第4天:JMM相關的一些概念
- 第5天:深入理解進程和線程
- 第6天:線程的基本操作
- 第7天:volatile與Java內存模型
- 第8天:線程組
- 第9天:用戶線程和守護線程
- 第10天:線程安全和synchronized關鍵字
- 第11天:線程中斷的幾種方式
- 第12天JUC:ReentrantLock重入鎖
- 第13天:JUC中的Condition對象
- 第14天:JUC中的LockSupport工具類,必備技能
- 第15天:JUC中的Semaphore(信號量)
- 第16天:JUC中等待多線程完成的工具類CountDownLatch,必備技能
- 第17天:JUC中的循環柵欄CyclicBarrier的6種使用場景
- 第18天:JAVA線程池,這一篇就夠了
- 第19天:JUC中的Executor框架詳解1
- 第20天:JUC中的Executor框架詳解2
- 第21天:java中的CAS,你需要知道的東西
- 第22天:JUC底層工具類Unsafe,高手必須要了解
- 第23天:JUC中原子類,一篇就夠了
- 第24天:ThreadLocal、InheritableThreadLocal(通俗易懂)
- 第25天:掌握JUC中的阻塞隊列
java高並發系列連載中,總計估計會有四五十篇文章。
阿里p7一起學並發,公眾號:路人甲java,每天獲取最新文章!