JUC中的AQS底層詳細超詳解( 二 )


AQS詳細資源獲取流程1. tryAcquire嘗試獲取資源AQS使用的設計模式是模板方法模式 。
具體代碼如下:
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 發現中斷過,則觸發中斷異常 selfInterrupt();}即AQS抽象基類AbstractQueuedSynchronizer給外部調用時,都是調的acquire(int arg)方法 。這個方法的內容是寫死的 。而acquire中 , 需要調用tryAcquire(arg) ,  這個方法是需要子類實現的,作用是判斷資源是否足夠獲取arg個
(下面部分代碼注釋選自: (2條消息) AQS子類的tryAcquire和tryRelease的實現_Mutou_ren的博客-CSDN博客_aqs tryacquire )
ReentrantLock中的tryAcquire實現這里暫時只談論一種容易理解的tryAcuire實現,其他附加特性的tryAcquire先不提 。
里面主要就做這幾件事:

  1. 獲取當前鎖的資源數
  2. 資源數為0 , 說明可以搶,確認是前置節點是頭節點,進行CAS試圖爭搶,搶成功就返回true , 并設置當前線程
  3. 沒搶成功,返回false
  4. 如果是重入的,則直接set設置增加后的狀態值,狀態值此時不一定為0和1了
protected final boolean tryAcquire(int acquires){ final Thread current = Thread.currentThread(); int c = getState(); // state==0代表當前沒有鎖,可以進行獲取 if (c == 0) { // 非公平才有的判斷,會判斷是否還有前驅節點,直接自己為頭節點了或者同步隊列空了才會繼續后面的鎖的獲取操作 if (!hasQueuedPredecessors() //CAS設置state為acquires , 成功后標記exclusiveOwnerThread為當前線程 && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } // 當前占有線程等于自己,代表重入 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; // 出現負數,說明溢出了 if (nextc < 0) // throw new Error("Maximum lock count exceeded"); // 因為是重入操作,可以直接進行state的增加 , 所以不需要CAS setState(nextc); return true; } return false;}2.addWaiter 添加到等待隊列當獲取資源失敗,會進行addWaiter(Node.EXCLUSIVE),arg) 。
目的是創建一個等待節點Node,并添加到等待隊列
private Node addWaiter(Node mode) { Node node = new Node(Thread.currentThread(), mode); // Try the fast path of enq; backup to full enq on failure Node pred = tail; if (pred != null) { node.prev = pred; // 通過CAS競爭隊尾 if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } // 競爭隊尾失敗 , 于是進行CAS頻繁循環競爭隊尾 enq(node); return node; } private Node enq(final Node node) { for (;;) { Node t = tail; if (t == null) { // Must initialize if (compareAndSetHead(new Node()))tail = head; } else { node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } }3. acquireQueued循環阻塞-競爭并在 "處于頭節點時嘗試獲取資源->睡眠->喚醒“中循環 。
當已經跑完任務的線程釋放資源時,會喚醒之前阻塞的線程 。
當被喚醒后,就會檢查自己是不是頭節點,如果不是,且認為可以阻塞,那就繼續睡覺去了
(下面代碼注釋部分選自AQS(acquireQueued(Node, int) 3)–隊列同步器 - 小窩蝸 - 博客園 (http://cnblogs.com) )
final boolean acquireQueued(final Node node, int arg) { // 標識是否獲取資源失敗 boolean failed = true; try { // 標識當前線程是否被中斷過 boolean interrupted = false; // 自旋操作 for (;;) { // 獲取當前節點的前繼節點 final Node p = node.predecessor(); // 如果前繼節點為頭結點,說明排隊馬上排到自己了,可以嘗試獲取資源,若獲取資源成功,則執行下述操作 if (p == head && tryAcquire(arg)) { // 將當前節點設置為頭結點 setHead(node); // 說明前繼節點已經釋放掉資源了,將其next置空,好讓虛擬機提前回收掉前繼節點 p.next = null; // help GC // 獲取資源成功,修改標記位failed = false; // 返回中斷標記 return interrupted; } // 若前繼節點不是頭結點 , 或者獲取資源失敗,// 則需要判斷是否需要阻塞該節點持有的線程 // 若可以阻塞 , 則繼續執行parkAndCheckInterrupt()函數, // 將該線程阻塞直至被喚醒 // 喚醒后會檢查是否已經被中斷 , 若返回true,則將interrupted標志置于true if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())interrupted = true; } } finally { // 最終獲取資源失敗,則當前節點放棄獲取資源 if (failed) cancelAcquire(node); } }4.shouldParkAfterFailedAcquire 檢查是否可以阻塞該方法不會直接阻塞線程 , 因為一旦線程掛起 , 后續就只能通過喚醒機制,中間還發生了內核態用戶態切換,消耗很大 。

推薦閱讀