Java 併發 --- 阻塞佇列總結
前面我們把阻塞佇列學習完了,現在過段時間來進行總結一下,也算是複習回顧知識,比較才能明白使用的場景。
ArrayBlockingQueue
Java 併發 — 阻塞佇列之ArrayBlockingQueue原始碼分析
ArrayBlockingQueue 是一個用陣列實現的有界阻塞佇列,此佇列按照先進先出(FIFO)的原則對元素進行操作。
ArrayBlockingQueue 中有兩個個重要的屬性,可重入鎖和Condition,可重入鎖是獨佔式的鎖,來保證對佇列的訪問都是執行緒安全的,阻塞操作,那麼什麼情況下該阻塞,什麼情況下不阻塞,這個是由Condition來控制的。
ArrayBlockingQueue 是有界的佇列,需要指定佇列的大小,它不會像ArrayList那樣自動擴容,ReentrantLock 有公平鎖和非公平鎖之分,因此需要指定,預設是非公平鎖。
ArrayBlockingQueue 執行緒安全,和Vector不一樣,Vector 中用的synchronized 關鍵字進行執行緒同步,ArrayBlockingQueue 中通過ReentrantLock來完成的。
ArrayBlockingQueue 是一種邏輯上的環形佇列。
ArrayBlockingQueue 在入隊和出隊上都使用了同一個重入鎖,因此入隊和出隊是不能併發執行的。
LinkedBlockingQueue
Java 併發 — 阻塞佇列之LinkedBlockingQueue原始碼分析
LinkedBlockingQueue 是一個用連結串列實現的有界阻塞佇列,此佇列按照先進先出(FIFO)的原則對元素進行操作。
LinkedBlockingQueue 和ArrayBlockingQueue 是差不多的,只是儲存方式由陣列轉換為連結串列了而已,LinkedBlockingQueue 也是通過重入鎖和Condition
來對佇列的操作訪問進行控制。
在ArrayBlockingQueue中,入隊和出隊都是用的同一個可重入鎖,而LinkedBlockingQueue 對於出隊和入隊使用了不同的可重入鎖來控制,ArrayBlockingQueue 入隊和出隊是不能同時併發的,而在LinkedBlockingQueue 中出隊和出隊是可以同時併發執行的(鎖不一樣)。
正是如此,對於count(佇列中元素的個數)使用了原子類AtomicInteger,來保證對count的操作具有原子性。
PriorityBlockingQueuey
Java 併發 — 阻塞佇列之PriorityBlockingQueuey原始碼分析
PriorityBlockingQueue 是一個支援優先順序的無界阻塞佇列,預設情況下元素採取自然順序升序排列,也可以自定義類實現compareTo()方法來指定元素排序規則,需要注意的是不能保證同優先順序元素的順序。
PriorityBlockingQueuey 是通過二叉堆來實現的,也是通過一個可重入鎖來控制入隊和出隊操作,保證執行緒安全。
PriorityBlockingQueue 是無界佇列,不會“隊滿”,實際當到達佇列最大值後(Integer.MAX_VALUE - 8),就拋oom異常了,因此這點在使用優先佇列的時候,需要注意。
二叉堆 使用的是陣列來實現,對一個二叉樹進行編號,然後按照順序存放在陣列中。
觀察一下父子之間的編號,會發現如果節點在陣列中的位置是i(i是節點在陣列中的下標), 則i節點對應的子節點在陣列中的位置分別是 2i + 1 和 2i + 2,同時i的父節點的位置為 (i-1)/2。
因此現在我們就把樹儲存到了陣列中,同時通過這種規律可以很快找到每個節點的父親和孩子節點。
關於二叉堆的更多操作,可以看上面的連結,這裡就不贅述了,簡單提一下就好。
DelayQueue
Java 併發 — 阻塞佇列之DelayQueue原始碼分析
DelayQueue 內部通過組合PriorityQueue 來實現儲存和維護元素順序的,其儲存元素必須實現Delayed 介面,通過實現Delayed 介面,可以獲取到元素延遲時間,以及可以比較元素大小(Delayed 繼承Comparable)
DelayQueue 通過一個可重入鎖來控制元素的入隊出隊行為
DelayQueue 中leader 標識 用於減少執行緒的競爭,表示當前有其它執行緒正在獲取隊頭元素。
PriorityQueue 只是負責儲存資料以及維護元素的順序,對於延遲時間取資料則是在DelayQueue 中進行判斷控制的。
SynchronousQueue
Java 併發 — 阻塞佇列之SynchronousQueue原始碼分析
SynchronousQueue 不是一個真正的佇列,其主要功能不是儲存元素,而且維護一個排隊的執行緒清單,這些執行緒等待把元素加入或者移除佇列。每一個執行緒的入隊(出隊)操作必須等待另一個執行緒的出隊(入隊)操作。
SynchronousQueue中的佇列不是針對資料的,而是針對操作,也就是入隊不一定就是入隊資料,而是入隊的操作,操作可以是put,也可以是take,put操作與take操作對應,可以互相匹配,put和put,take和take則是相同的操作(模式)。
SynchronousQueue 並沒有使用鎖來保證執行緒的安全,使用的是迴圈CAS方法。
SynchronousQueue有兩種模式:
1、公平模式
所謂公平就是遵循先來先服務的原則,因此其內部使用了一個FIFO佇列 來實現其功能。
2、非公平模式
SynchronousQueue 中的非公平模式是預設的模式,其內部使用棧來實現其功能,也就是 後來的先服務,
LinkedTransferQueue
Java 併發 — 阻塞佇列之LinkedTransferQueue原始碼分析
LinkedTransferQueue 和SynchronousQueue 其實基本是差不多的,兩者都是無鎖帶阻塞功能的佇列,SynchronousQueue 通過內部類Transferer 來實現公平和非公平佇列
在LinkedTransferQueue 中沒有公平與非公平的區分,LinkedTransferQueue 實現了TransferQueue介面,該介面定義的是帶阻塞操作的操作,相比SynchronousQueue 中的Transferer 功能更豐富。
LinkedTransferQueue是基於連結串列的FIFO無界阻塞佇列,它是JDK1.7才新增的阻塞佇列,有4種操作模式:
private static final int NOW = 0; // for untimed poll, tryTransfer
private static final int ASYNC = 1; // for offer, put, add
private static final int SYNC = 2; // for transfer, take
private static final int TIMED = 3; // for timed poll, tryTransfer
NOW :如果在取資料的時候,如果沒有資料,則直接返回,無需阻塞等待。
ASYNC:入隊的操作都不會阻塞,也就是說,入隊後執行緒會立即返回,不需要等到消費者執行緒來取資料。
SYNC :取資料的時候,如果沒有資料,則會進行阻塞等待。
TIMED : 取資料的時候,如果沒有資料,則會進行超時阻塞等待。
LinkedBlockingDeque
Java 併發 — 阻塞佇列之LinkedBlockingDeque原始碼分析
LinkedBlockingDeque是基於雙向連結串列的雙端有界阻塞佇列,預設使用非公平ReentrantLock實現執行緒安全,預設佇列最大長度都為Integer.MAX_VALUE;不允許null元素新增;雙端佇列可以用來實現 “竊取演算法” ,兩頭都可以操作佇列,相對於單端佇列可以減少一半的競爭。
LinkedBlockingDeque 是基於連結串列的,因此分析它實際就是分析連結串列而已,這個和我們前面LinkedBlockingQueue實質是差不多的,只是這裡是雙端佇列,可以兩頭操作佇列(隊尾也可以出隊,隊頭也可以入隊)。