淺談Java并發(fā) J.U.C之AQS:CLH同步隊列
CLH同步隊列是一個FIFO雙向隊列,AQS依賴它來完成同步狀態(tài)的管理,當(dāng)前線程如果獲取同步狀態(tài)失敗時,AQS則會將當(dāng)前線程已經(jīng)等待狀態(tài)等信息構(gòu)造成一個節(jié)點(Node)并將其加入到CLH同步隊列,同時會阻塞當(dāng)前線程,當(dāng)同步狀態(tài)釋放時,會把首節(jié)點喚醒(公平鎖),使其再次嘗試獲取同步狀態(tài)。
在CLH同步隊列中,一個節(jié)點表示一個線程,它保存著線程的引用(thread)、狀態(tài)(waitStatus)、前驅(qū)節(jié)點(prev)、后繼節(jié)點(next),其定義如下:
static final class Node {
/** 共享 */
static final Node SHARED = new Node();
/** 獨占 */
static final Node EXCLUSIVE = null;
/**
* 因為超時或者中斷,節(jié)點會被設(shè)置為取消狀態(tài),被取消的節(jié)點時不會參與到競爭中的,他會一直保持取消狀態(tài)不會轉(zhuǎn)變?yōu)槠渌麪顟B(tài);
*/
static final int CANCELLED = 1;
/**
* 后繼節(jié)點的線程處于等待狀態(tài),而當(dāng)前節(jié)點的線程如果釋放了同步狀態(tài)或者被取消,將會通知后繼節(jié)點,使后繼節(jié)點的線程得以運行
*/
static final int SIGNAL = -1;
/**
* 節(jié)點在等待隊列中,節(jié)點線程等待在Condition上,當(dāng)其他線程對Condition調(diào)用了signal()后,改節(jié)點將會從等待隊列中轉(zhuǎn)移到同步隊列中,加入到同步狀態(tài)的獲取中
*/
static final int CONDITION = -2;
/**
* 表示下一次共享式同步狀態(tài)獲取將會無條件地傳播下去
*/
static final int PROPAGATE = -3;
/** 等待狀態(tài) */
volatile int waitStatus;
/** 前驅(qū)節(jié)點 */
volatile Node prev;
/** 后繼節(jié)點 */
volatile Node next;
/** 獲取同步狀態(tài)的線程 */
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同步隊列結(jié)構(gòu)圖如下:

入列
學(xué)了數(shù)據(jù)結(jié)構(gòu)的我們,CLH隊列入列是再簡單不過了,無非就是tail指向新節(jié)點、新節(jié)點的prev指向當(dāng)前最后的節(jié)點,當(dāng)前最后一個節(jié)點的next指向當(dāng)前節(jié)點。代碼我們可以看看addWaiter(Node node)方法:
private Node addWaiter(Node mode) {
//新建Node
Node node = new Node(Thread.currentThread(), mode);
//快速嘗試添加尾節(jié)點
Node pred = tail;
if (pred != null) {
node.prev = pred;
//CAS設(shè)置尾節(jié)點
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//多次嘗試
enq(node);
return node;
}
addWaiter(Node node)先通過快速嘗試設(shè)置尾節(jié)點,如果失敗,則調(diào)用enq(Node node)方法設(shè)置尾節(jié)點
private Node enq(final Node node) {
//多次嘗試,直到成功為止
for (;;) {
Node t = tail;
//tail不存在,設(shè)置為首節(jié)點
if (t == null) {
if (compareAndSetHead(new Node()))
tail = head;
} else {
//設(shè)置為尾節(jié)點
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
在上面代碼中,兩個方法都是通過一個CAS方法compareAndSetTail(Node expect, Node update)來設(shè)置尾節(jié)點,該方法可以確保節(jié)點是線程安全添加的。在enq(Node node)方法中,AQS通過“死循環(huán)”的方式來保證節(jié)點可以正確添加,只有成功添加后,當(dāng)前線程才會從該方法返回,否則會一直執(zhí)行下去。
過程圖如下:

出列
CLH同步隊列遵循FIFO,首節(jié)點的線程釋放同步狀態(tài)后,將會喚醒它的后繼節(jié)點(next),而后繼節(jié)點將會在獲取同步狀態(tài)成功時將自己設(shè)置為首節(jié)點,這個過程非常簡單,head執(zhí)行該節(jié)點并斷開原首節(jié)點的next和當(dāng)前節(jié)點的prev即可,注意在這個過程是不需要使用CAS來保證的,因為只有一個線程能夠成功獲取到同步狀態(tài)。過程圖如下:

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家
相關(guān)文章
JavaWeb?使用DBUtils實現(xiàn)增刪改查方式
這篇文章主要介紹了JavaWeb?使用DBUtils實現(xiàn)增刪改查方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12
SpringDataRedis入門和序列化方式解決內(nèi)存占用問題小結(jié)
spring-data-redis是spring-data模塊的一部分,專門用來支持在spring管理項目對redis的操作,這篇文章主要介紹了SpringDataRedis入門和序列化方式解決內(nèi)存占用問題,需要的朋友可以參考下2022-12-12
SpringBoot使用郵箱發(fā)送驗證碼實現(xiàn)注冊功能
這篇文章主要為大家詳細(xì)介紹了SpringBoot使用郵箱發(fā)送驗證碼實現(xiàn)注冊功能實例,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-02-02
詳解SpringBoot如何優(yōu)雅的進(jìn)行前后端通信
現(xiàn)在的項目基本上都是前后端分離的項目,如何打通前后端,接收前端傳過來的參數(shù)呢,下面小編就來和大家詳細(xì)介紹一下SpringBoot如何優(yōu)雅的進(jìn)行前后端通信2024-03-03
使用Spring?Boot+gRPC構(gòu)建微服務(wù)并部署的案例詳解
這篇文章主要介紹了使用Spring?Boot+gRPC構(gòu)建微服務(wù)并部署,Spring Cloud僅僅是一個開發(fā)框架,沒有實現(xiàn)微服務(wù)所必須的服務(wù)調(diào)度、資源分配等功能,這些需求要借助Kubernetes等平臺來完成,本文給大家介紹的非常詳細(xì),需要的朋友參考下吧2022-06-06
Spring?IOC容器基于XML外部屬性文件的Bean管理
這篇文章主要為大家介紹了Spring?IOC容器Bean管理XML外部屬性文件,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05
SpringCloud 搭建企業(yè)級開發(fā)框架之實現(xiàn)多租戶多平臺短信通知服務(wù)(微服務(wù)實戰(zhàn))
這篇文章主要介紹了SpringCloud 搭建企業(yè)級開發(fā)框架之實現(xiàn)多租戶多平臺短信通知服務(wù),系統(tǒng)可以支持多家云平臺提供的短信服務(wù)。這里以阿里云和騰訊云為例,集成短信通知服務(wù),需要的朋友可以參考下2021-11-11

