在CLH鎖核心思想的影響下,JDK併發包以CLH鎖作為基礎而設計,其中主要是考慮到CLH鎖更容易實現取消與超時功能。比起原來的CLH鎖已經做了很大的改造,主要從兩方面進行了改造:節點的結構與節點等待機制。
在結構上引入了頭結點和尾節點,他們分別指向佇列的頭和尾,嘗試獲取鎖、入佇列、釋放鎖等實現都與頭尾節點相關,並且每個節點都引入前驅節點和後後續節點的引用;在等待機制上由原來的自旋改成阻塞喚醒。如圖,通過前驅後續節點的引用一節節連線起來形成一個連結串列佇列,對於頭尾節點的更新必須是原子的。下面詳細看看入隊、檢測掛起、釋放出隊、超時、取消等操作。
入隊
整塊邏輯其實是用一個無限迴圈進行CAS操作,即用自旋方式競爭直到成功。將尾節點tail的舊值賦予新節點node的前驅節點,並嘗試CAS操作將新節點node賦予尾節點tail,原先的尾節點的後續節點指向新建節點node。完成上面步驟就建立起一條如圖所示的連結串列佇列。程式碼簡化如下:
for (;;) {
Node t = tail;
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return node;
}
}複製程式碼
檢測掛起
上面我們說到節點等待機制已經被JDK併發作者由自旋機制改造成阻塞機制,一個新建的節點完成入隊操作後,如果是自旋則直接進入迴圈檢測前驅節點是否為頭結點即可,但現在被改為阻塞機制,當前執行緒將首先檢測是否為頭結點且嘗試獲取鎖,如果當前節點為頭結點併成功獲取鎖則直接返回,當前執行緒不進入阻塞,否則將當前執行緒阻塞。程式碼簡化如下:
for (;;) {
if (node.prev == head)
if(嘗試獲取鎖成功){
head=node;
node.next=null;
return;
}
阻塞執行緒
}複製程式碼
釋放出隊
出隊的主要工作是負責喚醒等待佇列中後續節點,讓所有等待節點環環相接,每條執行緒有序地往下執行。程式碼簡化如下:
Node s = node.next;
喚醒節點s包含的執行緒複製程式碼
超時
在支援超時的模式下需要LockSupport類的parkNanos方法支援,執行緒在阻塞一段時間後會自動喚醒,每次迴圈將累加消耗時間,當總消耗時間大於等於自定義的超時時間時就直接分返。程式碼簡化如下:
for (;;) {
嘗試獲取鎖
if (nanosTimeout <= 總消耗時間)
return;
LockSupport.parkNanos(this, nanosTimeout);
}複製程式碼
取消
佇列中等待鎖的佇列可能因為中斷或超時而涉及到取消操作,這種情況下被取消的節點不再進行鎖競爭。此過程主要完成的工作是將取消的節點移除,先將節點的。先將節點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);複製程式碼
====廣告時間,可直接跳過====
鄙人的新書《Tomcat核心設計剖析》已經在京東預售了,有需要的朋友可以到 item.jd.com/12185360.ht… 進行預定。感謝各位朋友。
=========================
歡迎關注: