Java併發編程之AQS(二)

從自定義獨佔鎖OnlyOneLock去看AQS源碼

先說明本文是基於JDK1.8的源碼分析,其他版本的JDK實現可能有所不同。
在自定義鎖中,我們覆蓋Lock接口的方法,AQS中我們覆蓋的幾個方法只需要處理好拿到鎖和沒有拿到鎖的邏輯,至於線程怎麼維護,我們卻不需要理會。



AQS如何實現獲取鎖的邏輯
我們自定義的獨佔鎖OnlyOneLock,在我們重寫了tryAcquire時,AQS的模板方法acquire內部會調用這個tryAcquire方法,然後維護隊列邏輯。

OnlyOneLock類的tryLock方法
  1. @Override
  2. public boolean tryLock() {
  3. return aqs.tryAcquire(1);
  4. }


tryLock調用了AQS的tryAcquire方法。剛好我們在OnlyOneLockAQS類中重寫了這個方法
  1. @Override
  2. protected boolean tryAcquire(int arg) {
  3. //狀態爲0時,設置爲1,表示當前線程獲取到鎖
  4. if(compareAndSetState(0,1)){
  5. //設置當前線程爲獲取到鎖的線程
  6. setExclusiveOwnerThread(Thread.currentThread());
  7. return true ;
  8. }
  9. return false;
  10. }


AQS的模板方法acquire內部會調用這個tryAcquire方法,然後維護隊列邏輯。
  1. public final void acquire(int arg) {
  2. if (!tryAcquire(arg) &&
  3. acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) //調用tryAcquire成功獲取鎖的話,就結束
  4. selfInterrupt(); //沒有獲取成功,調用addWaiter();把當前線程構建成一個Node加入到隊列尾部。
  5. }
Node.EXCLUSIVE是一個null結點。因爲我們這裏定義的是獨佔鎖。


上面調用了addWaiter方法。把當前線程構建成一個Node加入到隊列尾部。
  1. private Node addWaiter(Node mode) {
  2. Node node = new Node(Thread.currentThread(), mode); //新的結點
  3. // Try the fast path of enq; backup to full enq on failure
  4. Node pred = tail; //tail是原隊列的尾結點
  5. if (pred != null) { //原隊列不爲空
  6. node.prev = pred; //新節點node的前置結點是pred(原隊列的尾結點)
  7. if (compareAndSetTail(pred, node)) { //連接新的尾結點
  8. pred.next = node;
  9. return node;
  10. }
  11. }
  12. enq(node); //原隊列爲空的情況下,初始化隊列
  13. return node;
  14. }


調用addWaiter方法後,會再調用acquireQueued。
  1. final boolean acquireQueued(final Node node, int arg) {
  2. boolean failed = true;
  3. try {
  4. boolean interrupted = false;
  5. for (;;) {
  6. final Node p = node.predecessor(); //獲取node結點的前一個結點p
  7. if (p == head && tryAcquire(arg)) { //如果p是首節點head並且node代表的線程獲取鎖成功
  8. setHead(node); //新的隊首
  9. p.next = null; // help GC
  10. failed = false;
  11. return interrupted;
  12. }
  13. if (shouldParkAfterFailedAcquire(p, node) &&
  14. parkAndCheckInterrupt()) //等待
  15. interrupted = true;
  16. }
  17. } finally {
  18. if (failed)
  19. cancelAcquire(node);
  20. }
  21. }

acquireQueued方法可以總結如下:
一,先檢查當前線程在隊列中的前一個線程是否爲頭結點,如果是就用tryAcquire方法嘗試再拿一次。
二,上面的嘗試中沒有拿到鎖的話,會在parkAndCheckInterrupt時把自己給Park掉,Park大致可以理解爲休眠(Sleep)
三,Park的時候,線程就老老實實的在隊列裏面等着,等什麼呢?等獲取鎖的線程釋放鎖後通知它,它好繼續在for循環裏面獲取鎖。


自定義的獨佔鎖AQS維護鎖邏輯的大致過程流程圖如下。




因爲我們定義的是獨佔鎖,如果線程請求鎖直接成功,那它就不需要入隊列。如果沒有成功獲取,就要入隊列,隊列的首節點永遠是持有鎖的那個結點。



AQS如何實現釋放鎖的邏輯

AQS的內部類Node有一個重要的成員變量waitStatus。該變量用來表示當前節點代表的線程的狀態。初始時爲0。
  1. * The field is initialized to 0 for normal sync nodes, and
  2. * CONDITION for condition nodes. It is modified using CAS
  3. * (or when possible, unconditional volatile writes).
  4. */
  5. volatile int waitStatus;

以下是他的取值可能。
  1. static final int CANCELLED = 1; //取消
  2. static final int SIGNAL = -1; //該線程需要unpark
  3. static final int CONDITION = -2; //等待某個條件
  4. static final int PROPAGATE = -3;



OnlyOneLock重寫的unlock調用了AQS的release方法。
  1. @Override
  2. public void unlock() {
  3. aqs.release(1);
  4. }


AQS會調用tryRelease方法,因爲我們在OnlyOneLockAQS中重寫了這個方法,所以執行時,調用的是我們重寫後的tryRelease方法。
  1. public final boolean release(int arg) {
  2. if (tryRelease(arg)) { //如果釋放鎖成功
  3. Node h = head; //隊頭
  4. if (h != null && h.waitStatus != 0) //waitStatus!=0說明,線程在等待獲取鎖
  5. unparkSuccessor(h); //喚醒後繼節點
  6. return true;
  7. }
  8. return false;
  9. }


unparkSuccessor方法源碼如下。
  1. private void unparkSuccessor(Node node) {
  2. int ws = node.waitStatus;
  3. if (ws < 0)
  4. compareAndSetWaitStatus(node, ws, 0);
  5. Node s = node.next;
  6. if (s == null || s.waitStatus > 0) {
  7. s = null;
  8. for (Node t = tail; t != null && t != node; t = t.prev)
  9. if (t.waitStatus <= 0)
  10. s = t;
  11. }
  12. if (s != null)
  13. LockSupport.unpark(s.thread); //喚醒後繼結點
  14. }



參考:
https://juejin.im/post/5a3a09d9f265da4312810fb9
https://juejin.im/post/5a3c6aa551882538d3101d5f


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章