執行緒(十八)J.U.C之AQS:CLH同步佇列
在上篇部落格【死磕Java併發】—–J.U.C之AQS:AQS簡介中提到了AQS內部維護著一個FIFO佇列,該佇列就是CLH同步佇列。
CLH同步佇列是一個FIFO雙向佇列,AQS依賴它來完成同步狀態的管理,當前執行緒如果獲取同步狀態失敗時,AQS則會將當前執行緒已經等待狀態等資訊構造成一個節點(Node)並將其加入到CLH同步佇列,同時會阻塞當前執行緒,當同步狀態釋放時,會把首節點喚醒(公平鎖),使其再次嘗試獲取同步狀態。
在CLH同步佇列中,一個節點表示一個執行緒,它儲存著執行緒的引用(thread)、狀態(waitStatus)、前驅節點(prev)、後繼節點(next),其定義如下:
static final class Node {
/** 共享 */
static final Node SHARED = new Node();
/** 獨佔 */
static final Node EXCLUSIVE = null;
/**
* 因為超時或者中斷,節點會被設定為取消狀態,被取消的節點時不會參與到競爭中的,他會一直保持取消狀態不會轉變為其他狀態;
*/
static final int CANCELLED = 1;
/**
* 後繼節點的執行緒處於等待狀態,而當前節點的執行緒如果釋放了同步狀態或者被取消,將會通知後繼節點,使後繼節點的執行緒得以執行
*/
static final int SIGNAL = -1;
/**
* 節點在等待佇列中,節點執行緒等待在Condition上,當其他執行緒對Condition呼叫了signal()後,改節點將會從等待佇列中轉移到同步佇列中,加入到同步狀態的獲取中
*/
static final int CONDITION = -2;
/**
* 表示下一次共享式同步狀態獲取將會無條件地傳播下去
*/
static final int PROPAGATE = -3;
/** 等待狀態 */
volatile int waitStatus;
/** 前驅節點 */
volatile Node prev;
/** 後繼節點 */
volatile Node next;
/** 獲取同步狀態的執行緒 */
volatile Thread thread;
Node nextWaiter;
final boolean isShared() {
return nextWaiter == SHARED;
}
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() {
}
Node(Thread thread, Node mode) {
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) {
this.waitStatus = waitStatus;
this.thread = thread;
}
}
CLH同步佇列結構圖如下:
入列
學了資料結構的我們,CLH佇列入列是再簡單不過了,無非就是tail指向新節點、新節點的prev指向當前最後的節點,當前最後一個節點的next指向當前節點。程式碼我們可以看看addWaiter(Node node)方法:
private Node addWaiter(Node mode) {
//新建Node
Node node = new Node(Thread.currentThread(), mode);
//快速嘗試新增尾節點
Node pred = tail;
if (pred != null) {
node.prev = pred;
//CAS設定尾節點
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//多次嘗試
enq(node);
return node;
}
addWaiter(Node node)先通過快速嘗試設定尾節點,如果失敗,則呼叫enq(Node node)方法設定尾節點
private Node enq(final Node node) {
//多次嘗試,直到成功為止
for (;;) {
Node t = tail;
//tail不存在,設定為首節點
if (t == null) {
if (compareAndSetHead(new Node()))
tail = head;
} else {
//設定為尾節點
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
在上面程式碼中,兩個方法都是通過一個CAS方法compareAndSetTail(Node expect, Node update)來設定尾節點,該方法可以確保節點是執行緒安全新增的。在enq(Node node)方法中,AQS通過“死迴圈”的方式來保證節點可以正確新增,只有成功新增後,當前執行緒才會從該方法返回,否則會一直執行下去。
過程圖如下:
出列
CLH同步佇列遵循FIFO,首節點的執行緒釋放同步狀態後,將會喚醒它的後繼節點(next),而後繼節點將會在獲取同步狀態成功時將自己設定為首節點,這個過程非常簡單,head執行該節點並斷開原首節點的next和當前節點的prev即可,注意在這個過程是不需要使用CAS來保證的,因為只有一個執行緒能夠成功獲取到同步狀態。過程圖如下:
相關文章
- iOS 多執行緒--GCD 序列佇列、併發佇列以及同步執行、非同步執行iOS執行緒GC佇列非同步
- iOS執行緒、同步非同步、序列並行佇列iOS執行緒非同步並行佇列
- 多執行緒系列(十八) -AQS原理淺析執行緒AQS
- AQS佇列同步器AQS佇列
- AbstractQueuedSynchronizer 佇列同步器(AQS)佇列AQS
- Java 佇列同步器 AQSJava佇列AQS
- Java JUC 抽象同步佇列AQS解析Java抽象佇列AQS
- 主佇列&主執行緒佇列執行緒
- java多執行緒:執行緒池原理、阻塞佇列Java執行緒佇列
- 多執行緒,執行緒類三種方式,執行緒排程,執行緒同步,死鎖,執行緒間的通訊,阻塞佇列,wait和sleep區別?執行緒佇列AI
- 【Interview】什麼是AQS佇列同步器ViewAQS佇列
- 原始碼級深挖AQS佇列同步器原始碼AQS佇列
- Python實現執行緒安全佇列Python執行緒佇列
- 多執行緒學習-Disruptor佇列執行緒佇列
- java執行緒池-工作佇列workQueueJava執行緒佇列
- java多執行緒:執行緒同步synchronized(不同步的問題、佇列與鎖),死鎖的產生和解決Java執行緒synchronized佇列
- 執行緒系列四AQS執行緒AQS
- 併發容器J.U.C -- AQS同步元件(二)AQS元件
- Java多執行緒之執行緒同步【synchronized、Lock、volatitle】Java執行緒synchronized
- 列表與佇列——談談執行緒安全佇列執行緒
- 執行緒池的阻塞佇列的理解執行緒佇列
- J.U.C - AQSAQS
- 執行緒池中的最大執行緒數、核心執行緒數和佇列大小的合理設定執行緒佇列
- Java 中佇列同步器 AQS(AbstractQueuedSynchronizer)實現原理Java佇列AQS
- Java BlockingQueue 阻塞佇列[用於多執行緒]JavaBloC佇列執行緒
- Java多執行緒進階(二九)—— J.U.C之collections框架:ConcurrentLinkedQueueJava執行緒框架
- 執行緒安全佇列(使用互斥鎖進行實現)執行緒佇列
- Java同步之執行緒池詳解Java執行緒
- Java 併發程式設計 ----- AQS(抽象佇列同步器)Java程式設計AQS抽象佇列
- Java 併發程式設計 —– AQS(抽象佇列同步器)Java程式設計AQS抽象佇列
- 證明執行緒池ThreadPoolExecutor的核心執行緒數,最大執行緒數,佇列長度的關係執行緒thread佇列
- 多執行緒和多執行緒同步執行緒
- 任務與佇列 iOS之多執行緒GCD(一)佇列iOS執行緒GC
- C++自用小輪子——執行緒安全佇列C++執行緒佇列
- 深入淺出Java多執行緒(十三):阻塞佇列Java執行緒佇列
- Java幾種執行緒池及任務佇列Java執行緒佇列
- C#資料結構-執行緒安全佇列C#資料結構執行緒佇列
- 踩坑 Spring Cloud Hystrix 執行緒池佇列配置SpringCloud執行緒佇列