摘要:JDK1.5及之后的版本中,提供的執行緒安全的容器,一般被稱為并發容器,與同步容器一樣,并發容器在總體上也可以分為四大類,分別為:List、Set、Map和Queue,
本文分享自華為云社區《【高并發】要想學好并發編程,這些并發容器的坑是你必須要注意的!!(建議收藏)》,作者:冰 河 ,
其實,在JDK1.5之前的執行緒安全的容器,大多數都是指同步容器,使用同步容器進行并發編程時,最大的問題就是性能很差,因為同步容器中的所有方法都是使用synchronized鎖進行互斥,串行度太高了,無法真正的做到并行,
所以,在JDK1.5之后,JDK中提供了并發性能更好的容器,JDK1.5及之后的版本中,提供的執行緒安全的容器,一般被稱為并發容器,
并發容器
與同步容器一樣,并發容器在總體上也可以分為四大類,分別為:List、Set、Map和Queue,總體上如下圖所示,
接下來,我們分別介紹下這些并發容器在使用時的注意事項和避免踩到的坑,
List
并發容器中的List相對來說比較簡單,就一個CopyOnWriteArrayList,大家可以從字面的意思中就能夠體會到:CopyOnWrite,在寫的時候進行復制操作,也就是說在進行寫操作時,會將共享變數復制一份,那這樣做有什么好處呢?最大的好處就是:讀操作可以做到完全無鎖化,
在CopyOnWriteArrayList內部維護了一個陣列,成員變數array指向這個陣列,其核心源代碼如下所示,
private transient volatile Object[] array; final Object[] getArray() { return array; } final void setArray(Object[] a) { array = a; }
當進行操作時,都是基于array指向的這個內部陣列進行的,例如,我們使用Iterator迭代器遍歷這個陣列時,會按照下圖所示的方式進行讀操作,
如果在遍歷CopyOnWriteArrayList時發生寫操作,例如,向陣列中增加一個元素時,CopyOnWriteArrayList則會將內部的陣列復制一份出來,然后會在新復制出來的陣列上添加新的元素,添加完再將array指向新的陣列,如下圖所示,
對于CopyOnWriteArrayList的其他寫操作和添加元素的操作原理相同,這里就不再贅述了,
使用CopyOnWriteArrayList時需要注意的是:
- CopyOnWriteArrayList只適合寫操作比較少的場景,并且能夠容忍讀寫操作在短時間內的不一致,
- CopyOnWriteArrayList的迭代器是只讀的,不支持寫操作,
Set
對于Set介面來說,并發容器中主要有兩個實作類,一個是CopyOnWriteArraySet,另一個是ConcurrentSkipListSet,其中,CopyOnWriteArraySet的使用場景、原理與注意事項和CopyOnWriteArrayList一致,而ConcurrentSkipListSet的使用場景、原理和注意事項和下文的ConcurrentSkipListMap一致,這里,我就不再贅述啦,
Map
在并發容器中,Map介面的實作類主要有ConcurrentHashMap和ConcurrentSkipListMap,而ConcurrentHashMap和ConcurrentSkipListMap最大的區別就是:ConcurrentHashMap的Key是無序的,而ConcurrentSkipListMap的Key是有序的,
在使用ConcurrentHashMap和ConcurrentSkipListMap時,需要注意的是:ConcurrentHashMap和ConcurrentSkipListMap的Key和Value都不能為空,
這里,我們可以將Map相關的類總結成一個表格,如下所示,
這樣,大家記憶起來就方便多了,
這里,ConcurrentSkipListMap是基于“跳表”實作的,跳表的插入、洗掉、查詢的平均時間復雜度為O(log n),這些時間復雜度在理論上與執行緒數沒有關系,如果要追求性能的話,可以嘗試使用ConcurrentSkipListMap,
Queue
在Java的并發容器中,Queue相對來說比較復雜,我們先來了解幾個概念:
- 阻塞佇列:阻塞一般就是指當佇列已滿時,入隊操作會阻塞;當佇列為空時,出隊操作就會阻塞,
- 非阻塞佇列:佇列的入隊和出隊操作不會阻塞,
- 單端佇列:佇列的入隊操作只能在隊尾進行,佇列的出隊操作只能在隊首進行,
- 雙端佇列:佇列的入隊操作和出隊操作都可以在隊首和隊尾進行,
我們可以將上述的佇列進行組合,將佇列分為單端阻塞佇列、雙端阻塞佇列、單端非阻塞佇列和雙端非阻塞佇列,
在Java的并發容器中,會使用明顯的標識來區分不同型別的佇列,
- 阻塞佇列一個明顯的標識就是使用Blocking修飾,例如,ArrayBlockingQueue和LinkedBlockingQueue都是阻塞佇列,
- 單端佇列會使用Queue標識,例如ArrayBlockingQueue和LinkedBlockingQueue也是單端佇列,
- 雙端佇列會使用Deque標識,例如LinkedBlockingDeque和ConcurrentLinkedDeque都是雙端佇列,
接下來,我們就分別簡單聊聊這四種型別的佇列,
單端阻塞佇列
在Java的并發容器中,單端阻塞佇列的主要實作是BlockingQueue,主要包括:ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue、LinkedTransferQueue、PriorityBlockingQueue和DelayQueue,
單端阻塞佇列的內部一般會有一個佇列,
在實作上,內部的佇列可以是陣列,例如ArrayBlockingQueue,也可以是鏈表,例如LinkedBlockingQueue,
也可以在內部不存在佇列,例如SynchronousQueue,SynchronousQueue實作了生產者的入隊操作必須等待消費者的出隊操作完成之后才能進行,
LinkedTransferQueue集成了LinkedBlockingQueue和SynchronousQueue的優點,并且性能比LinkedBlockingQueue好,
PriorityBlockingQueue實作了按照優先級進行出隊操作,也就是說,佇列元素在PriorityBlockingQueue內部可以按照某種規則進行排序,
DelayQueue是延時佇列,實作了在一段時間后再出隊的操作,
雙端阻塞佇列
雙端阻塞佇列的實作主要是LinkedBlockingDeque,示意圖如下所示,
單端非阻塞佇列
單端非阻塞佇列的實作主要是ConcurrentLinkedQueue,示意圖如下所示,
雙端非阻塞佇列
雙端非阻塞佇列的實作主要是ConcurrentLinkedDeque,示意圖如下所示,
有界與無界佇列
使用佇列時,還要注意佇列的有界與無界問題,也就是在使用佇列時,需要注意佇列是否有容量限制,
在實際作業中,一般推薦使用有界佇列,因為無界佇列很容易導致記憶體溢位的問題,在Java的并發容器中,只有ArrayBlockingQueue和LinkedBlockingQueue支持有界,其他的佇列都是無界佇列,
在使用時,一定要注意記憶體溢位問題,
總結
今天我們主要介紹了JDK1.5之后提供的并發容器,主要包括:List、Set、Map和Queue,而Queue又可以分為:單端阻塞佇列、雙端阻塞佇列、單端非阻塞佇列和雙端非阻塞佇列,對于每種并發容器,我們簡單介紹了其基本原理和注意事項,
點擊關注,第一時間了解華為云新鮮技術~
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/503079.html
標籤:其他
