摘要:二什么是同步隊列同步隊列一個雙向隊列,隊列中每個節點等待前驅節點釋放共享狀態鎖被喚醒就可以了。三入列操作如上圖了解了同步隊列的結構,我們在分析其入列操作在簡單不過。
一、寫在前面
在上篇我們聊到AQS的原理,具體參見《J.U.C|AQS原理》。
這篇我們來給大家聊聊AQS中核心同步隊列(CLH)。
二、什么是同步隊列(CLH)同步隊列
一個FIFO雙向隊列,隊列中每個節點等待前驅節點釋放共享狀態(鎖)被喚醒就可以了。
AQS如何使用它?
AQS依賴它來完成同步狀態的管理,當前線程如果獲取同步狀態失敗時,AQS則會將當前線程已經等待狀態等信息構造成一個節點(Node)并將其加入到CLH同步隊列,同時會阻塞當前線程,當同步狀態釋放時,會把首節點喚醒(公平鎖),使其再次嘗試獲取同步狀態。
Node節點面貌?
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調用singnal()方法后,該節點加入到同步隊列中。 */ static final int CONDITION = -2; /** * 表示下一次共享式獲取同步狀態的時會被無條件的傳播下去。 */ static final int PROPAGATE = -3; /**等待狀態*/ volatile int waitStatus; /**前驅節點 */ volatile Node prev; /**后繼節點*/ volatile Node next; /**獲取同步狀態的線程 */ volatile Thread thread; /**鏈接下一個等待狀態 */ Node nextWaiter; // 下面一些方法就不貼了 }
CLH同步隊列的結構圖
這里是基于CAS(保證線程的安全)來設置尾節點的。
如上圖了解了同步隊列的結構, 我們在分析其入列操作在簡單不過。無非就是將tail(使用CAS保證原子操作)指向新節點,新節點的prev指向隊列中最后一節點(舊的tail節點),原隊列中最后一節點的next節點指向新節點以此來建立聯系,來張圖幫助大家理解。
源碼
源碼我們可以通過AQS中的以下兩個方法來了解下
addWaiter方法
private Node addWaiter(Node mode) { // 以給定的模式來構建節點, mode有兩種模式 // 共享式SHARED, 獨占式EXCLUSIVE; Node node = new Node(Thread.currentThread(), mode); // 嘗試快速將該節點加入到隊列的尾部 Node pred = tail; if (pred != null) { node.prev = pred; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } // 如果快速加入失敗,則通過 anq方式入列 enq(node); return node; }
先通過addWaiter(Node node)方法嘗試快速將該節點設置尾成尾節點,設置失敗走enq(final Node node)方法
enq
private Node enq(final Node node) { // CAS自旋,直到加入隊尾成功 for (;;) { Node t = tail; if (t == null) { // 如果隊列為空,則必須先初始化CLH隊列,新建一個空節點標識作為Hader節點,并將tail 指向它 if (compareAndSetHead(new Node())) tail = head; } else {// 正常流程,加入隊列尾部 node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } }
通過“自旋”也就是死循環的方式來保證該節點能順利的加入到隊列尾部,只有加入成功才會退出循環,否則會一直循序直到成功。
上述兩個方法都是通過compareAndSetHead(new Node())方法來設置尾節點,以保證節點的添加的原子性(保證節點的添加的線程安全。)
四、出列操作同步隊列(CLH)遵循FIFO,首節點是獲取同步狀態的節點,首節點的線程釋放同步狀態后,將會喚醒它的后繼節點(next),而后繼節點將會在獲取同步狀態成功時將自己設置為首節點,這個過程非常簡單。如下圖
設置首節點是通過獲取同步狀態成功的線程來完成的(獲取同步狀態是通過CAS來完成),只能有一個線程能夠獲取到同步狀態,因此設置頭節點的操作并不需要CAS來保證,只需要將首節點設置為其原首節點的后繼節點并斷開原首節點的next(等待GC回收)應用即可。
五、總結聊完后我們來總一下,同步隊列就是一個FIFO雙向對隊列,其每個節點包含獲取同步狀態失敗的線程應用、等待狀態、前驅節點、后繼節點、節點的屬性類型以及名稱描述。
其入列操作也就是利用CAS(保證線程安全)來設置尾節點,出列就很簡單了直接將head指向新頭節點并斷開老頭節點聯系就可以了。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/74241.html
摘要:造成當前線程在接到信號被中斷或到達指定最后期限之前一直處于等待狀態。該線程從等待方法返回前必須獲得與相關的鎖。如果線程已經獲取了鎖,則將喚醒條件隊列的首節點。 一、寫在前面 在前幾篇我們聊了 AQS、CLH、ReentrantLock、ReentrantReadWriteLock等的原理以及其源碼解讀,具體參見專欄 《非學無以廣才》 這章我們一起聊聊顯示的Condition 對象。 ...
摘要:本章我們主要聊獨占式即同一時刻只能有一個線程獲取同步狀態,其它獲取同步狀態失敗的線程則會加入到同步隊列中進行等待。到這獨占式獲取同步和釋放同步狀態的源碼已經分析完了。 一、寫在前面 上篇文章通過ReentrantLock 的加鎖和釋放鎖過程給大家聊了聊AQS架構以及實現原理,具體參見《J.U.C|AQS的原理》。 理解了原理,我們在來看看再來一步一步的聊聊其源碼是如何實現的。 本章給...
摘要:接著線程過來通過方式獲取鎖,獲取鎖的過程就是通過操作變量將其值從變為。線程加鎖成功后還有一步重要的操作,就是將設置成為自己。線程屁顛屁顛的就去等待區小憩一會去了。 一、寫在前面 這篇文章,我們聊一聊Java并發中的核武器, AQS底層實現。 不管是工作三四年、還是五六年的在工作或者面試中涉及到并發的是時候總是繞不過AQS這個詞。 首先,確實還有很多人連AQS是什么都不知道,甚至有的竟...
摘要:二什么是重入鎖可重入鎖,顧名思義,支持重新進入的鎖,其表示該鎖能支持一個線程對資源的重復加鎖。將由最近成功獲得鎖,并且還沒有釋放該鎖的線程所擁有??梢允褂煤头椒▉頇z查此情況是否發生。 一、寫在前面 前幾篇我們具體的聊了AQS原理以及底層源碼的實現,具體參見 《J.U.C|一文搞懂AQS》《J.U.C|同步隊列(CLH)》《J.U.C|AQS獨占式源碼分析》《J.U.C|AQS共享式源...
摘要:在時,引入了包,該包中的大多數同步器都是基于來構建的??蚣芴峁┝艘惶淄ㄓ玫臋C制來管理同步狀態阻塞喚醒線程管理等待隊列。指針用于在結點線程被取消時,讓當前結點的前驅直接指向當前結點的后驅完成出隊動作。 showImg(https://segmentfault.com/img/remote/1460000016012438); 本文首發于一世流云的專欄:https://segmentfau...
閱讀 2906·2021-11-15 18:02
閱讀 3800·2021-10-14 09:43
閱讀 3732·2021-09-08 10:41
閱讀 2522·2019-08-30 15:53
閱讀 1803·2019-08-30 14:14
閱讀 1943·2019-08-29 16:12
閱讀 3138·2019-08-29 14:03
閱讀 1280·2019-08-29 13:46