阻塞佇列 LinkedTransferQueue 原始碼解析
LinkedTransferQueue
-
LinkedTransferQueue是下面三者的集合體
- LinkedBlockingQueue(支援阻塞,即失敗入隊,但不再支援獲取 Lock 的阻塞了,因為是基於 cas 的)
- SynchronousQueue(公平模式,實現了 Transfer 介面)
- ConcurrentLinkedQueue (支援非阻塞,因為傳統的阻塞佇列都是使用的 Lock,想要 offer 或者 poll 等操作必須先獲得 lock,而非阻塞是 直接cas 一次,失敗就直接返回,不用阻塞獲得鎖)
-
四種模式
-
NOW,立即返回,沒有匹配到立即返回,不做入隊操作
- 對應的方法有:poll()、tryTransfer(e)
-
ASYNC,非同步,元素入隊但當前執行緒不會阻塞(相當於無界LinkedBlockingQueue的元素入隊)
- 對應的方法有:add(e)、offer(e)、put(e)、offer(e, timeout, unit)
-
SYNC,同步,元素入隊後當前執行緒阻塞,等待被匹配到
- 對應的方法有:take()、transfer(e)
-
TIMED,有超時,元素入隊後等待一段時間被匹配,時間到了還沒匹配到就返回元素本身
- 對應的方法有:poll(timeout, unit)、tryTransfer(e, timeout, unit)
-
-
底層:所有方法最終呼叫的都是 xfer(),這個方法和 synchronousQueue 的 transfer 方法一樣,都是基於 cas 無鎖的,會先檢查是否符合匹配規則,即佇列所有節點是否是資料節點和請求不同,如果不同的話會遍歷匹配,遍歷是為了防止併發下被其他執行緒先匹配了,如果請求和佇列都是資料節點或者都不是資料節點,則不能進行匹配的話,如果是 Now 模式就立即返回失敗了,其他模式會把該模式的這個請求入隊,入隊採用 cas 保證一定成功,在入隊成功之後如果是 Async 模式(即入隊請求),那麼也就直接返回了,剩下的 Time 和 Sync 模式,會阻塞等待匹配的結果返回,即使用 LockSupport 定時阻塞,然後檢查 item 是否發生了交換,發生了就返回
屬性
public class LinkedTransferQueue<E> extends AbstractQueue<E>
implements TransferQueue<E>, java.io.Serializable {
// 頭節點
transient volatile Node head;
// 尾節點
private transient volatile Node tail;
// 放取元素的幾種方式:
// 立即返回,用於非超時的poll()和tryTransfer()方法中
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
// 超時,用於有超時的poll()和tryTransfer()方法中
private static final int TIMED = 3; // for timed poll, tryTransfer
static final class Node {
// 是否是資料節點(也就標識了是生產者還是消費者)
final boolean isData; // false if this is a request node
// 元素的值
volatile Object item; // initially non-null if isData; CASed to match
// 下一個節點
volatile Node next;
// 持有元素的執行緒
volatile Thread waiter; // null until waiting
}
// ...
構造方法
public LinkedTransferQueue() {
}
public LinkedTransferQueue(Collection<? extends E> c) {
this();
addAll(c);
}
入隊
四個方法都是一樣的,使用 非同步的方式 呼叫xfer()方法,因為是無界佇列,當然不會等待了
public void put(E e) {
// 非同步模式,不會阻塞,不會超時
// 因為是放元素,單連結串列儲存,會一直往後加
xfer(e, true, ASYNC, 0);
}
public boolean offer(E e, long timeout, TimeUnit unit) {
xfer(e, true, ASYNC, 0);
return true;
}
public boolean offer(E e) {
xfer(e, true, ASYNC, 0);
return true;
}
public boolean add(E e) {
xfer(e, true, ASYNC, 0);
return true;
}
出隊
出隊的四個方法也是直接或間接的呼叫 xfer() 方法,只是模式不同
public E remove() {
E x = poll();
if (x != null)
return x;
else
throw new NoSuchElementException();
}
public E take() throws InterruptedException {
// 同步模式,會阻塞直到取到元素
E e = xfer(null, false, SYNC, 0);
if (e != null)
return e;
Thread.interrupted();
throw new InterruptedException();
}
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
// 有超時時間
E e = xfer(null, false, TIMED, unit.toNanos(timeout));
if (e != null || !Thread.interrupted())
return e;
throw new InterruptedException();
}
public E poll() {
// 立即返回,沒取到元素返回null
return xfer(null, false, NOW, 0);
}
移交元素
public boolean tryTransfer(E e) {
// 立即返回
return xfer(e, true, NOW, 0) == null;
}
public void transfer(E e) throws InterruptedException {
// 同步模式
if (xfer(e, true, SYNC, 0) != null) {
Thread.interrupted(); // failure possible only due to interrupt
throw new InterruptedException();
}
}
public boolean tryTransfer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
// 有超時時間
if (xfer(e, true, TIMED, unit.toNanos(timeout)) == null)
return true;
if (!Thread.interrupted())
return false;
throw new InterruptedException();
}
xfer()
/**
* e:表示元素;
* haveData:表示是否是資料節點,
* how:表示放取元素的方式,上面提到的四種,NOW、ASYNC、SYNC、TIMED;
* nanos:表示超時時間;
*/
private E xfer(E e, boolean haveData, int how, long nanos) {
// 不允許放入空元素
if (haveData && (e == null))
throw new NullPointerException();
Node s = null; // the node to append, if needed
// 外層迴圈,自旋,失敗就重試
retry:
for (;;) { // restart on append race
// 佇列中節點肯定全是資料節點或者全都不是資料節點,所以如果當前請求和佇列現在是否資料節點匹配(即不同),
// 那麼就遍歷進行匹配,因為可能有併發導致匹配失敗,所以要遍歷。
for (Node h = head, p = h; p != null;) { // find & match first node
// p節點的模式
boolean isData = p.isData;
// p節點的值
Object item = p.item;
// p 沒有被取消,再校驗一下是否已經被其他執行緒匹配
if (item != p && (item != null) == isData) { // unmatched
// 如果兩者都是資料節點或者都不是資料節點,則不能匹配,直接挑出迴圈後嘗試入隊
if (isData == haveData) // can't match
break;
// 否則一個資料節點,一個非資料節點,則嘗試匹配,即 cas 更新 item
if (p.casItem(item, e)) { // match
// 沒被其他執行緒競爭,匹配成功,下面更新 head,即下一次要匹配的節點。
for (Node q = p; q != h;) {
// 當前節點是 q ,下一個要匹配的節點是 q.next
Node n = q.next; // update by 2 unless singleton
// head 已被當前執行緒匹配,將其更新為下一個
if (head == h && casHead(h, n == null ? q : n)) {
h.forgetNext();
break;
} // advance and retry
// 更新 head 失敗會執行下面
// 如果新的頭節點為空,或者其next為空,或者其next未匹配,就重試
if ((h = head) == null ||
(q = h.next) == null || !q.isMatched())
break; // unless slack < 2
}
// 喚醒p中等待的執行緒
LockSupport.unpark(p.waiter);
// 並返回匹配到的元素
return LinkedTransferQueue.<E>cast(item);
}
}
// 執行到這說明, p 已經被匹配了或者嘗試匹配的時候失敗了
Node n = p.next;
p = (p != n) ? n : (h = head); // Use head if p offlist
}
/**
* 到這裡肯定是佇列中儲存的節點型別和自己一樣,或者佇列中沒有元素了,那麼就入隊(不管放元素還是取元素都得入隊)
* 入隊又分成四種情況
*/
// 如果不是立即返回
if (how != NOW) { // No matches available
// 新建s節點
if (s == null)
s = new Node(e, haveData);
// 嘗試入隊
Node pred = tryAppend(s, haveData);
// 入隊失敗,重試
if (pred == null)
continue retry; // lost race vs opposite mode
// 如果不是非同步(同步或者有超時),就等待匹配,與返回匹配的結果
// 該方法同 SynchrnousQueue,會呼叫 LookSupport 定時阻塞,然後檢查當前節點的 item 屬性是否發聲了交換
if (how != ASYNC)
return awaitMatch(s, pred, e, (how == TIMED), nanos);
}
return e; // not waiting
}
}
LinkedTransferQueue與SynchronousQueue(公平模式)有什麼異同呢?
(1)在java8中兩者的實現方式基本一致,都是使用的雙重佇列;
(2)前者完全實現了後者,但比後者更靈活;
(3)後者不管放元素還是取元素,如果沒有可匹配的元素,所在的執行緒都會阻塞;
(4)前者可以自己控制放元素是否需要阻塞執行緒,比如使用四個新增元素的方法就不會阻塞執行緒,只入隊元素,使用transfer()會阻塞執行緒;
(5)取元素兩者基本一樣,都會阻塞等待有新的元素進入被匹配到;
相關文章
- Java併發包原始碼學習系列:阻塞佇列實現之LinkedTransferQueue原始碼解析Java原始碼佇列
- 阻塞佇列 SynchronousQueue 原始碼解析佇列原始碼
- 阻塞佇列 DelayQueue 原始碼解析佇列原始碼
- 阻塞佇列 PriorityBlockingQueue 原始碼解析佇列BloC原始碼
- Laravel 佇列原始碼解析Laravel佇列原始碼
- Java併發包原始碼學習系列:阻塞佇列實現之ArrayBlockingQueue原始碼解析Java原始碼佇列BloC
- Java併發包原始碼學習系列:阻塞佇列實現之LinkedBlockingQueue原始碼解析Java原始碼佇列BloC
- Java併發包原始碼學習系列:阻塞佇列實現之PriorityBlockingQueue原始碼解析Java原始碼佇列BloC
- Java併發包原始碼學習系列:阻塞佇列實現之SynchronousQueue原始碼解析Java原始碼佇列
- Java併發包原始碼學習系列:阻塞佇列實現之LinkedBlockingDeque原始碼解析Java原始碼佇列BloC
- Java併發包原始碼學習系列:阻塞佇列實現之DelayQueue原始碼解析Java原始碼佇列
- 佇列、阻塞佇列佇列
- 阻塞佇列一——java中的阻塞佇列佇列Java
- 探討阻塞佇列和執行緒池原始碼佇列執行緒原始碼
- 原始碼剖析ThreadPoolExecutor執行緒池及阻塞佇列原始碼thread執行緒佇列
- Java併發包原始碼學習系列:基於CAS非阻塞併發佇列ConcurrentLinkedQueue原始碼解析Java原始碼佇列
- 從原始碼解析-Android資料結構之單向阻塞佇列LinkedBlockingQueue的使用原始碼Android資料結構佇列BloC
- 迴歸Java基礎:LinkedBlockingQueue阻塞佇列解析JavaBloC佇列
- 阻塞佇列 BlockingQueue佇列BloC
- 阻塞佇列--LinkedBlockingQueue佇列BloC
- 死磕阻塞佇列佇列
- [原始碼解析] 訊息佇列 Kombu 之 基本架構原始碼佇列架構
- 多執行緒程式設計-分析阻塞佇列的原始碼實現執行緒程式設計佇列原始碼
- 延遲阻塞佇列 DelayQueue佇列
- 阻塞佇列BlockingQueue(三)--DelayQueue佇列BloC
- Java中的阻塞佇列Java佇列
- 阻塞佇列——四組API佇列API
- 原始碼解析Synchronous Queue 這種特立獨行的佇列原始碼佇列
- AQS原始碼深入分析之條件佇列-你知道Java中的阻塞佇列是如何實現的嗎?AQS原始碼佇列Java
- 聊聊併發(四)——阻塞佇列佇列
- Java中常用的七個阻塞佇列第二篇DelayQueue原始碼介紹Java佇列原始碼
- Java併發包原始碼學習系列:阻塞佇列BlockingQueue及實現原理分析Java原始碼佇列BloC
- laravel原始碼分析-佇列QueueLaravel原始碼佇列
- [原始碼解析] 分散式任務佇列 Celery 之啟動 Consumer原始碼分散式佇列
- 阻塞佇列 BlockingQueue(二)--ArrayBlockingQueue與LinkedBlockingQueue佇列BloC
- Java併發系列 — 阻塞佇列(BlockingQueue)Java佇列BloC
- Java併發——阻塞佇列集(上)Java佇列
- Java併發——阻塞佇列集(下)Java佇列