JDK併發AQS系列(五)

weixin_33797791發表於2018-11-01

CLH鎖的改進

鑑於自旋鎖的不足,Craig,Landin,Hagersten發明了CLH鎖。而在CLH鎖核心思想的影響下,Java併發包的基礎框架AQS以CLH鎖作為基礎而設計,其中主要是考慮到CLH鎖更容易實現取消與超時功能。

比起原來的CLH鎖已經做了很大的改造,主要從兩方面進行了改造:

  1. 節點的結構與節點等待機制。在結構上引入了頭結點和尾節點,他們分別指向佇列的頭和尾,嘗試獲取鎖、入佇列、釋放鎖等實現都與頭尾節點相關,並且每個節點都引入前驅節點和後後續節點的引用;
  2. 在等待機制上由原來的自旋改成阻塞喚醒。如圖,通過前驅後續節點的引用一節節連線起來形成一個連結串列佇列,對於頭尾節點的更新必須是原子的。

下面詳細看看入隊、檢測掛起、釋放出隊、超時、取消等操作。

入隊操作

入隊,整塊邏輯其實是用一個無限迴圈進行CAS操作,即用自旋方式競爭直到成功。將尾節點tail的舊值賦予新節點node的前驅節點,並嘗試CAS操作將新節點node賦予尾節點tail,原先的尾節點的後續節點指向新建節點node。完成上面步驟就建立起一條如上圖的連結串列佇列。程式碼簡化如下:

for (;;) {
   Node t = tail;
   node.prev = t;
   if (compareAndSetTail(t, node)) {
      t.next = node;
      return node;
   }
}
複製程式碼

檢測掛起

檢測掛起,上面我們說到節點等待機制已經被AQS作者由自旋機制改造成阻塞機制,一個新建的節點完成入隊操作後,如果是自旋則直接進入迴圈檢測前驅節點是否為頭結點即可,但現在被改為阻塞機制,當前執行緒將首先檢測是否為頭結點且嘗試獲取鎖,如果當前節點為頭結點併成功獲取鎖則直接返回,當前執行緒不進入阻塞,否則將當前執行緒阻塞。程式碼簡化如下:

for (;;) {
    if (node.prev == head)
        if(嘗試獲取鎖成功){
            head=node;
            node.next=null;
            return;
        }
   阻塞執行緒
}
複製程式碼

釋放出隊

釋放出隊,出隊的主要工作是負責喚醒等待佇列中後續節點,讓所有等待節點環環相接,每條執行緒有序地往下執行。

如果在共享模式下出隊工作將變得異常複雜,主要考慮的是對釋放時競爭優化而引入了另外一種狀態PROPAGATE,多條執行緒併發釋放時可能將頭結點狀態改為PROPAGATE,當下一節點被喚醒時根據此狀態將繼續往下喚醒而不用去執行嘗試獲取,達到優化效果。此處只討論獨佔模式,程式碼簡化如下:

Node s = node.next;
喚醒節點s包含的執行緒
複製程式碼

超時操作

超時,在支援超時的模式下需要LockSupport類的parkNanos方法支援,執行緒在阻塞一段時間後會自動喚醒,每次迴圈將累加消耗時間,當總消耗時間大於等於自定義的超時時間時就直接分返。程式碼簡化如下:

for (;;) {
   嘗試獲取鎖
   if (nanosTimeout <= 總消耗時間)
      return;
   LockSupport.parkNanos(this, nanosTimeout);
 }④超時,在支援超時的模式下需要LockSupport類的parkNanos方法支援,執行緒在阻塞一段時間後會自動喚醒,每次迴圈將累加消耗時間,當總消耗時間大於等於自定義的超時時間時就直接分返。程式碼簡化如下:
複製程式碼

取消操作

取消,佇列中等待鎖的佇列可能因為中斷或超時而涉及到取消操作,這種情況下被取消的節點不再進行鎖競爭。

此過程主要完成的工作是將取消的節點移除。先將節點node狀態設定成取消,再將前驅節點pred的後續節點指向node的後續節點,這裡由於涉及到競爭,必須通過CAS進行操作,CAS操作就算失敗也不必理會,因為已經改了節點的狀態,在嘗試獲取鎖操作中會迴圈對節點的狀態判斷。

node.waitStatus = Node.CANCELLED;
Node pred = node.prev;
Node predNext = pred.next;
Node next = node.next;
compareAndSetNext(pred, predNext, next);⑤取消,佇列中等待鎖的佇列可能因為中斷或超時而涉及到取消操作,這種情況下被取消的節點不再進行鎖競爭。此過程主要完成的工作是將取消的節點移除。先將節點node狀態設定成取消,再將前驅節點pred的後續節點指向node的後續節點,這裡由於涉及到競爭,必須通過CAS進行操作,CAS操作就算失敗也不必理會,因為已經改了節點的狀態,在嘗試獲取鎖操作中會迴圈對節點的狀態判斷。
複製程式碼

-------------推薦閱讀------------

我的開源專案彙總(機器&深度學習、NLP、網路IO、AIML、mysql協議、chatbot)

為什麼寫《Tomcat核心設計剖析》

我的2017文章彙總——機器學習篇

我的2017文章彙總——Java及中介軟體

我的2017文章彙總——深度學習篇

我的2017文章彙總——JDK原始碼篇

我的2017文章彙總——自然語言處理篇

我的2017文章彙總——Java併發篇


跟我交流,向我提問:

歡迎關注:

相關文章