1. 程式人生 > >死磕Java併發:J.U.C之AQS:CLH同步佇列

死磕Java併發:J.U.C之AQS:CLH同步佇列

640?wx_fmt=jpeg

本文轉載自公號:Java技術驛站

在上篇文章中提到了AQS內部維護著一個FIFO佇列,該佇列就是CLH同步佇列。

CLH同步佇列是一個FIFO雙向佇列,AQS依賴它來完成同步狀態的管理,當前執行緒如果獲取同步狀態失敗時,AQS則會將當前執行緒已經等待狀態等資訊構造成一個節點(Node)並將其加入到CLH同步佇列,同時會阻塞當前執行緒,當同步狀態釋放時,會把首節點喚醒(公平鎖),使其再次嘗試獲取同步狀態。

在CLH同步佇列中,一個節點表示一個執行緒,它儲存著執行緒的引用(thread)、狀態(waitStatus)、前驅節點(prev)、後繼節點(next),其定義如下:

staticfinalclass
Node{/** 共享 */staticfinalNode SHARED =newNode();/** 獨佔 */staticfinalNode EXCLUSIVE =null;/**     * 因為超時或者中斷,節點會被設定為取消狀態,被取消的節點時不會參與到競爭中的,他會一直保持取消狀態不會轉變為其他狀態;     */staticfinalint CANCELLED =1;/**     * 後繼節點的執行緒處於等待狀態,而當前節點的執行緒如果釋放了同步狀態或者被取消,將會通知後繼節點,使後繼節點的執行緒得以執行     */staticfinalint SIGNAL    =-1;/**     * 節點在等待佇列中,節點執行緒等待在Condition上,當其他執行緒對Condition呼叫了signal()後,改節點將會從等待佇列中轉移到同步佇列中,加入到同步狀態的獲取中     */
staticfinalint CONDITION =-2;/**     * 表示下一次共享式同步狀態獲取將會無條件地傳播下去     */staticfinalint PROPAGATE =-3;/** 等待狀態 */volatileint waitStatus;/** 前驅節點 */volatileNode prev;/** 後繼節點 */volatileNodenext;/** 獲取同步狀態的執行緒 */volatileThread thread;Node nextWaiter;finalboolean isShared(){return nextWaiter == SHARED;}finalNode predecessor
()throwsNullPointerException{Node p = prev;if(p ==null)thrownewNullPointerException();elsereturn 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同步佇列結構圖如下:

640?wx_fmt=png

1、入列

學了資料結構的我們,CLH佇列入列是再簡單不過了,無非就是tail指向新節點、新節點的prev指向當前最後的節點,當前最後一個節點的next指向當前節點。

程式碼我們可以看看addWaiter(Node node)方法:

privateNode addWaiter(Node mode){//新建NodeNode node =newNode(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)方法設定尾節點。

privateNode enq(finalNode node){//多次嘗試,直到成功為止for(;;){Node t = tail;//tail不存在,設定為首節點if(t ==null){if(compareAndSetHead(newNode()))
                    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通過“死迴圈”的方式來保證節點可以正確新增,只有成功新增後,當前執行緒才會從該方法返回,否則會一直執行下去。

過程圖如下:

640?wx_fmt=png

2、出列

CLH同步佇列遵循FIFO,首節點的執行緒釋放同步狀態後,將會喚醒它的後繼節點(next),而後繼節點將會在獲取同步狀態成功時將自己設定為首節點,這個過程非常簡單,head執行該節點並斷開原首節點的next和當前節點的prev即可,注意在這個過程是不需要使用CAS來保證的,因為只有一個執行緒能夠成功獲取到同步狀態。

過程圖如下:

640?wx_fmt=png

- END -

 往期推薦:

  • 死磕Java系列:

……

  • Spring系列:

……

號外:

最近在做幾個有意思的開源專案,感興趣的朋友可以看看。

地址:

https://github.com/dyc87112/swagger-butler

可關注我的公眾號

640?wx_fmt=jpeg

深入交流、更多福利

掃碼加入我的知識星球

640?wx_fmt=png

點選“閱讀原文”,看本號其他精彩內容