AQS核心流程解析cancelAcquire方法
引出問題
首先,先考慮一個問題,什么條件會觸發(fā)cancelAcquire()方法?
cancelAcquire()方法的反向查找
可以清楚的看到在互斥鎖和共享鎖的拿鎖過程中都是有調(diào)用此方法的,而cancelAcquire()方法是寫在finally代碼塊中,并且使用failed標(biāo)志位來控制cancelAcquire()方法的執(zhí)行??梢缘贸觯谟|發(fā)異常的情況下會執(zhí)行cancelAcquire()方法。
響應(yīng)中斷的獲鎖方法
可以清楚的看到,這里是響應(yīng)異常,如果發(fā)生了異常,比如中斷異常,那么當(dāng)前線程Node需要做出取消的操作,那么下面詳細的說明cancelAcquire()方法。
private void cancelAcquire(Node node) { // Ignore if node doesn't exist if (node == null) return; // 當(dāng)前節(jié)點的線程指向置為null node.thread = null; // 協(xié)同取消的處理。 // 這里是判斷當(dāng)前節(jié)點的上一個節(jié)點的狀態(tài)是否是取消狀態(tài)(狀態(tài)大于0只有是取消狀態(tài)) // 如果上一個節(jié)點是取消狀態(tài),那么繼續(xù)往上遍歷,直到找到狀態(tài)為小于0的狀態(tài)節(jié)點。 // 并且把當(dāng)前節(jié)點的prev指向非取消節(jié)點。 Node pred = node.prev; while (pred.waitStatus > 0) node.prev = pred = pred.prev; // 得到?jīng)]有取消節(jié)點的下一個節(jié)點。 Node predNext = pred.next; // 因為當(dāng)前cancelAcquire()方法就是取消的處理 // 所以將當(dāng)前節(jié)點設(shè)置為取消狀態(tài)。 node.waitStatus = Node.CANCELLED; // 如果當(dāng)前取消的節(jié)點是tail節(jié)點,也就是最后一個節(jié)點 // 那么就把tail指針指向上面while循環(huán)遍歷出的prev節(jié)點(因為要指向一個沒有被取消的節(jié)點)。 if (node == tail && compareAndSetTail(node, pred)) { // help GC // 為什么說help GC呢? // 因為把prev的next節(jié)點設(shè)置為null, // 這樣GC ROOT掃描發(fā)現(xiàn)沒有根節(jié)點的引用。 compareAndSetNext(pred, predNext, null); } else { // 走到else代表當(dāng)前節(jié)點不是tail節(jié)點,或者是cas操作的時候tail發(fā)生了變化 // 如果不是tail節(jié)點,不能直接把tail節(jié)點指向到上面while循環(huán)得出的prev節(jié)點 int ws; // 這里是的if代碼塊,是為了嘗試一次,如果不成功再去復(fù)雜的處理。 // 這里的if判斷條件如下: // 1.如果上面while循環(huán)得到的prev節(jié)點不是head節(jié)點 // 2.如果上面while循環(huán)得到的prev節(jié)點為-1,如果不為-1,cas改變成-1也。 // 3.如果上面while循環(huán)得到的rpev節(jié)點的線程指向不為null(如果為null代表在取消的過程中) // 因為&&是拼接,所以上面任意一個條件為false就會進入到else條件中。 if (pred != head && ((ws = pred.waitStatus) == Node.SIGNAL || (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) && pred.thread != null) { // 進到這里代表這次嘗試成功了。 // 得到當(dāng)前節(jié)點的下一個節(jié)點 // 然后把前面while循環(huán)得到的prev節(jié)點的next指向當(dāng)前節(jié)點的next節(jié)點。 Node next = node.next; if (next != null && next.waitStatus <= 0) compareAndSetNext(pred, predNext, next); } else { // 直接喚醒當(dāng)前節(jié)點的下一個節(jié)點。 // 喚醒的目的是為了去執(zhí)行shouldParkAfterFailedAcquire方法去處理取消節(jié)點。 unparkSuccessor(node); } // 把當(dāng)前節(jié)點的下一個節(jié)點指向自己. // help gc。 node.next = node; // help GC } }
比較復(fù)雜,所以筆者為了讀者的觀看順利, 下面會拆分步驟,并且畫圖來理解。
跳過已經(jīng)取消的節(jié)點,找到一個非取消的節(jié)點
// Skip cancelled predecessors // 跳過已經(jīng)被取消的節(jié)點 Node pred = node.prev; while (pred.waitStatus > 0) node.prev = pred = pred.prev;
更新正常節(jié)點的鏈表
當(dāng)前取消節(jié)點是tail節(jié)點的情況
if (node == tail && compareAndSetTail(node, pred)) { compareAndSetNext(pred, predNext, null); } else { // 后面講 }
當(dāng)前取消節(jié)點是非tail節(jié)點的情況
// 當(dāng)前取消的節(jié)點不為tail節(jié)點的情況 int ws; // if的邏輯可以理解為嘗試一次。 // 代表while循環(huán)得到的prev節(jié)點不是head節(jié)點 // 代表while循環(huán)得到的prev節(jié)點是可喚醒的正常節(jié)點 // 節(jié)點while循環(huán)得到的prev節(jié)點不是待取消節(jié)點 if (pred != head && ((ws = pred.waitStatus) == Node.SIGNAL || (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) && pred.thread != null) { Node next = node.next; // 當(dāng)前節(jié)點的下一個節(jié)點也是正常的情況(非取消) if (next != null && next.waitStatus <= 0) compareAndSetNext(pred, predNext, next); } else { // 嘗試失敗,只能走比較狠的邏輯去處理了。 unparkSuccessor(node); } node.next = node; // help GC
如果if的判斷能通過,那就代表當(dāng)前這次嘗試是成功的,成功了就把鏈表都鏈上。
問題來了,為什么這里不把Node.next(取消節(jié)點的下一個節(jié)點)和Node(當(dāng)前取消節(jié)點)的鏈給斷開,不斷開的話,JVM是無法回收掉Node(當(dāng)前取消節(jié)點),那不是內(nèi)存泄漏了?
Doug Lea這里是不是寫的有問題?
nonono.
當(dāng)正常喚醒節(jié)點時,搶到鎖的節(jié)點會執(zhí)行
private void setHead(Node node) { head = node; node.thread = null; node.prev = null; }
看到else條件下unparkSuccessor(node);的執(zhí)行邏輯。
這里已經(jīng)相當(dāng)難描述清楚,筆者會執(zhí)行流程和代碼都已經(jīng)寫明白。
這里其實就是一個喚醒操作,喚醒的意義在于去執(zhí)行shouldParkAfterFailedAcquire()方法從后往前遍歷找到一個waitStatus不為1的節(jié)點,然后鏈起來。
cancelAcquire |->unparkSuccessor(node當(dāng)前取消節(jié)點) |->LockSupport.unpark(node.next.thread) |->shouldParkAfterFailedAcquire(node(取消的節(jié)點),node.next(取消的節(jié)點的下一個節(jié)點))
// Node pred 是取消的節(jié)點 // Node node 是取消的節(jié)點的下一個節(jié)點 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { // int ws = pred.waitStatus; if (ws == Node.SIGNAL) return true; // 遍歷找到一個非取消的節(jié)點 // 并且把當(dāng)前取消的節(jié)點的下一個節(jié)點與找到的非取消節(jié)點雙向鏈起來。 if (ws > 0) { do { // 往前走 node.prev = pred = pred.prev; // 鏈起來 } while (pred.waitStatus > 0); // 循環(huán)條件,所以找到一個小于等于0的節(jié)點就會退出 pred.next = node; // 鏈起來 } else { compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; }
最終喚醒節(jié)點,走到shouldParkAfterFailedAcquire()方法中。從后往前的遍歷找到正常的節(jié)點
到此這篇關(guān)于AQS核心流程解析cancelAcquire方法的文章就介紹到這了,更多相關(guān)AQS cancelAcquire內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring?Data?JPA框架的核心概念與Repository接口詳解
Spring?Data?JPA是Spring基于JPA規(guī)范的基礎(chǔ)上封裝的?套?JPA?應(yīng)?框架,可使開發(fā)者?極簡的代碼即可實現(xiàn)對數(shù)據(jù)庫的訪問和操作,本篇我們來了解Spring?Data?JPA框架的核心概念與Repository接口2022-04-04Java Scala偏函數(shù)與偏應(yīng)用函數(shù)超詳細講解
Scala是一種多范式的編程語言,支持面向?qū)ο蠛秃瘮?shù)式編程。Scala也支持異常處理,即在程序運行過程中發(fā)生意外或錯誤時,采取相應(yīng)的措施2023-04-04springboot2.3 整合mybatis-plus 高級功能(圖文詳解)
這篇文章主要介紹了springboot2.3 整合mybatis-plus 高級功能,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-08-08