ReentrantLock 與 AQS 源碼分析

ReentrantLock 與 AQS 源碼分析

1. 基本結(jié)構(gòu)

?? 重入鎖 ReetrantLock,JDK 1.5新增的類,作用與synchronized關(guān)鍵字相當,但比synchronized更加靈活。ReetrantLock本身也是一種支持重進入的鎖,即該鎖可以支持一個線程對資源重復加鎖,但是加鎖多少次,就必須解鎖多少次,這樣才可以成功釋放鎖。

1. 繼承

沒有繼承任何類,因為很多操作都使用了組合完成。

2. 實現(xiàn)

Lock, java.io.Serializable
??這里著重介紹一下 Lock 接口,接口定義了幾個必要的方法,也是在 ReentrantLock 中的重點需要分析的方法。
?? 三類方法:獲取鎖、釋放鎖、獲取條件。

public interface Lock {
    // 阻塞獲取鎖,如果獲取不到鎖就一直等待
    void lock();
    // 可中斷獲取鎖,在獲取鎖的過程可以被中斷,但是 Synchronized 是不可以
    void lockInterruptibly() throws InterruptedException;
    // 非阻塞獲取鎖,沒有獲取到鎖立即返回
    boolean tryLock();
    // 超時獲取鎖,沒獲取到鎖等待一段時間
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    // 解鎖
    void unlock();
    // 等待喚醒機制的條件
    Condition newCondition();
}

從上面可以看到 Synchronized 和 Lock 的一些重要區(qū)別:

  1. Lock 的獲取鎖的過程是可以中斷的,Synchronized 不可以,Synchronized 只能在 wait或同步代碼塊執(zhí)行過程中才可以被中斷。

  2. 由于 Lock 顯示的加鎖,鎖可以橫跨幾個方法,也就是臨界區(qū)的位置可以更加自由。

  3. Lock 支持超時獲取鎖。

  4. 后面會看到 Lock 還支持公平及非公平鎖。

  5. 綁定多個 Condition 條件

3. 主要字段

??很好,這個類的字段非常的少,真正起作用的字段只有一個 “鎖” 字段。

    // 同步鎖
    private final Sync sync;

?? 這個鎖(Sync)是一個繼承自 AQS 的抽象內(nèi)部類,說明一下 AQS (AbstractQueuedSynchronizer) 一般被稱為隊列同步器,他是并發(fā)包中的核心組件,絕大多數(shù)鎖機制都是采用的這個類來實現(xiàn)的。雖然看到他是一個抽象類,但是你會發(fā)現(xiàn)里面沒有一個方法是抽象方法,他實現(xiàn)了鎖機制中的必要的通用的方法,待會會專門講這個類。不然 ReentrantLock 沒辦法說,ReentrantLock 里面的鎖操作都是依賴于 AQS。

?? 然后這個鎖是有兩個子類,分別是 NonfairSyncFairSync 從名字上也可以看出這兩個類分別代表了 公平鎖非公平鎖 。何為鎖的公平性? 實際上就是新來的線程需要征用鎖必須要要等到先于他到達的線程獲取并釋放鎖。也就是獲取鎖的過程是按照下來后到的順序進行的,反之就稱為非公平鎖。后面我們會看到其實這兩種鎖不同就在于非公平鎖在新線程創(chuàng)建后首先會直接進行鎖的獲取,如果沒有獲取到會進行一段時間的自旋,始終沒獲取到鎖才進行等待狀態(tài)。

?? 一般而言,公平鎖開銷比非公平鎖大,這也是比較符合我們的直觀感受。公平鎖是需要進行排隊的,但在某些場景下,可能更注重時間先后順序,那么公平鎖自然是很好的選擇。

?? 好總結(jié)一下,在 ReentrantLock 中只維護了一個 “鎖” 變量,這個鎖是繼承了 AQS 同步器,然后這個鎖又有兩種派生的鎖:公平鎖,非公平鎖。那么 ReentrantLock 實現(xiàn)其實就有兩種方式:公平鎖,非公平鎖。

4. 主要方法概覽

  1. ctor-2
  2. lock
  3. lockInterruptibly
  4. tryLock
  5. tryLock(time)
  6. unlock
  7. newCondition

2. 基礎(chǔ)并發(fā)組件 AQS

1. 基本字段

1. 重要字段

?? AQS 是維護了一個同步隊列(雙向鏈表),這個隊列里面線程都是需要競爭鎖的,沒有競爭到的就在同步隊列中等待。headtail 就指向隊列的首尾。state 是一個標志字段,表示當前有多少線程在臨界區(qū)。一般來說 state 只能是 0 或 1 但是由于鎖是可重入的,所以也有大于 1 的情況。

?? 除了一個同步隊列還有 0~n 個等待隊列,等待隊列就是調(diào)用了 await 方法的線程,會被掛到調(diào)用了 awaitcondition 上面的等待隊列,所以有多少個 condition 就有多少等待隊列。

    //同步隊列頭指針
    private transient volatile Node head;
    // 同步隊列尾指針
    private transient volatile Node tail;
    // 狀態(tài)標志,0 則沒有線程在臨界區(qū),非零表示有 state 個線程在臨界區(qū)(由于鎖可重入)
    private volatile int state;

2. Node 節(jié)點

??Node 節(jié)點也就是上文所提到的 同步隊列等待隊列 中的元素,注意兩個隊列之間的元素類型是一樣的因為他們之間會有相互移動轉(zhuǎn)換的動作,這兩個隊列中的元素自然是線程,為了方便查找和表示 AQS 將線程封裝到了 Node 節(jié)點中,構(gòu)成雙向隊列。

static final class Node {
        // 共享非 null/獨占為 null  
        static final Node SHARED = new Node();
        static final Node EXCLUSIVE = null;

        /**
         * 線程狀態(tài)
         */
        static final int CANCELLED =  1;
        static final int SIGNAL    = -1;
        static final int CONDITION = -2;
        static final int PROPAGATE = -3;
        volatile int waitStatus;
       // 雙向鏈表  這兩個指針用于同步隊列構(gòu)建鏈表使用的   下面還有一個 nextWaiter 是用來構(gòu)建等待單鏈表隊列
        volatile Node prev;
        volatile Node next;
        // 線程
        volatile Thread thread;
        // 等待隊列單鏈表
        Node nextWaiter;

        /**
         * Returns true if node is waiting in shared mode.
         */
        final boolean isShared() {
            return nextWaiter == SHARED;
        }

    }

?? 可以看到上面有一個 waitStatus 屬性,代表了線程當前的狀態(tài),狀態(tài)標識就是那些常量。具體如下:

  1. SIGNAL: 正在執(zhí)行的線程結(jié)束釋放鎖或者被取消執(zhí)行,他必須喚醒后續(xù)的狀態(tài)為 SIGNAL 節(jié)點

  2. CANCELLED: 在同步隊列中等待的線程等待超時或被中斷,需要從同步隊列中取消該Node的結(jié)點, 其結(jié)點的waitStatus為CANCELLED,即結(jié)束狀態(tài),進入該狀態(tài)后的結(jié)點將不會再變化。

  3. CONDITION: 該標識的結(jié)點處于等待隊列中(不是同步隊列),結(jié)點的線程等待在Condition上,當其他線程調(diào)用了Condition的signal()方法后,CONDITION狀態(tài)的結(jié)點將從等待隊列轉(zhuǎn)移到同步隊列中,等待獲取同步鎖。

  4. PROPAGATE:在共享模式中,該狀態(tài)標識結(jié)點的線程處于可運行狀態(tài)。

  5. 0:代表初始化狀態(tài)。

?? 可以看到,Node 里面的主要字段就是一個狀態(tài)標志位、一個線程的引用、用于構(gòu)建鏈表的指針。注意,有三個指針,其中前兩個 nextpre 是用來構(gòu)建同步隊列的(雙向鏈表),后面 nextWaiter 是用來構(gòu)建等待隊列。所以說雖然同步隊列和等待隊列使用的同一個數(shù)據(jù)類型,數(shù)據(jù)結(jié)構(gòu)是不同的,并且在后面我們會看到等待隊列中的節(jié)點只有兩種狀態(tài) ConditionCANCELLED 前者表示線程已結(jié)束需要從等待隊列中移除,后者表示條件結(jié)點等待被喚醒。

??下面畫圖說明一下同步隊列和等待隊列的情況。
等待隊列


image

同步隊列


image

3. ConditionObject

?? 這個內(nèi)部類是等待喚醒機制的核心,在他上面綁定了一個等待隊列。在這個類中使用了兩個指針( firstWaiter/lastWaiter )指向隊列的首尾。這里主要看一下 awaitsignalsignalAll 方法。

  1. await
    ?? 當一個線程調(diào)用了await()相關(guān)的方法,那么首先構(gòu)建一個Node節(jié)點封裝當前線程的相關(guān)信息加入到等待隊列中進行等待,并釋放鎖直到被喚醒(移動到同步隊列)、中斷、超時才被隊列中移出。被喚醒后的第一件事是搶鎖和檢查是否被中斷,然后才是移除隊列。被喚醒時候的狀態(tài)應該為 SIGNAL ,而在方法中執(zhí)行的移除隊列的操作就是移除狀態(tài)非 Condition 的節(jié)點。
public final void await() throws InterruptedException {
            // 等待可中斷
            if (Thread.interrupted())
                throw new InterruptedException();
            // 加入等待隊列, new 新的 Node 做一個尾插入
            Node node = addConditionWaiter();
            // 釋放當前線程的鎖,失敗則將當前線程設置為取消狀態(tài)
            int savedState = fullyRelease(node);

            int interruptMode = 0;
            // 如果沒在同步隊列就讓線程等待也就是看是否被喚醒
            // 如果有中斷或者被喚醒那么退出循環(huán)
            while (!isOnSyncQueue(node)) {
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            // 運行到此處說明已經(jīng)被喚醒了,因為結(jié)束了循環(huán)
            // 喚醒后,首先自旋一下獲取鎖,同時判斷是否中斷
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            // 清理隊列中狀態(tài)不是 Condition 的的任務,包括被喚醒的 SIGNAL 和 被取消的 CANCELLED
            if (node.nextWaiter != null)
                unlinkCancelledWaiters();
            //被中斷 拋異常
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }
  1. signal/doSignal/signalAll
    ?? 執(zhí)行 signal 首先進行鎖的判斷,如果沒有獲取到獨占鎖就直接拋出異常。這也就是為什么只有擁有鎖的線程才能執(zhí)行 signal ,然后獲取等待隊列中的第一個節(jié)點執(zhí)行 doSignal。
        public final void signal() {
            // 獲取獨占鎖
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            // 喚醒等待隊里中的第一個線程
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }

?? doSignal 方法主要就干了三個事 :

  1. 將被喚醒的節(jié)點從等待隊列中移除(while 循環(huán)體),如果被喚醒的節(jié)點被取消了就繼續(xù)喚醒后面的節(jié)點(transferForSignal 返回 false)
  2. 否則把這個節(jié)點加入到同步隊列 ( enq 方法 )
  3. 當同步隊列中當前節(jié)點的前驅(qū)被取消或者沒辦法喚醒時則喚醒這個線程 ( unpark ),這時候調(diào)用了 unpark 正好和 await 中的 park 相對應使得 await 的線程被喚醒,接著執(zhí)行循環(huán)體判斷自己已經(jīng)被移入到同步隊列了,接著就可以執(zhí)行后面的獲取鎖的操作。
 private void doSignal(Node first) {
            do {
                // 頭指針指向喚醒節(jié)點的下一個節(jié)點,并順便判斷等待隊列是否空
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                // 解除引用
                first.nextWaiter = null;
            } while (!transferForSignal(first) && (first = firstWaiter) != null); //移入同步隊列失敗則繼續(xù)喚醒下一個線程,否則喚醒成功
            // 喚醒成功的線程不一定馬上能開始執(zhí)行,只有在前驅(qū)節(jié)點被取消或者沒辦法被喚醒時
   }
   
   
   //  將節(jié)點從等待隊列移動到同步隊列   成功返回 true 失敗 false
    final boolean transferForSignal(Node node) {
        // 在等待隊列中的節(jié)點只有 condition 和 cancelled 兩種狀態(tài),如果狀態(tài)更新失敗說明任務被取消
        // 否則更新為初始狀態(tài)   直接返回的話上面的 doSignal 就會繼續(xù)喚醒后面的線程
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;
        // 把當前節(jié)點加入同步隊列
        Node p = enq(node);
        // 獲取同步隊列中倒數(shù)第二個節(jié)點的狀態(tài),當前節(jié)點的前驅(qū)
        int ws = p.waitStatus;
        // 如果前驅(qū)節(jié)點被取消或者在設置前驅(qū)節(jié)點狀態(tài)為Node.SIGNAL狀態(tài)失敗時,喚醒被通知節(jié)點代表的線程
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }
    
    
    // 插入一個節(jié)點到同步隊列,如果同步隊列是空的則加入一個空節(jié)點做為頭結(jié)點
    // 死循環(huán)保證肯定能插入    返回插入節(jié)點的前驅(qū)
    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                // 這一步不需要 cas 是因為并發(fā)沒關(guān)系,只是指向鏈表結(jié)尾,不會多線程更新問題
                node.prev = t;
                // 可能有多個線程搶
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

?? 有一個小問題,就是在某個線程中執(zhí)行了別人的 signal 不會導致當前線程立即放棄鎖,之所以會這樣正是由于 ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL) 這個判斷,即前驅(qū)線程都結(jié)束了。比如下面的例子:

package util.AQSTest;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

// test signal 執(zhí)行后不會導致當前線程立即釋放鎖
public class AQSTest {
    static Lock lock = new ReentrantLock();
   static Condition run1Cond = lock.newCondition();
    static Condition run2Cond = lock.newCondition();

    static class Runner1 implements Runnable {
        @Override
        public void run() {
            lock.lock();
            try {
                System.out.println("runner 1 start");
                run1Cond.await(1, TimeUnit.SECONDS);
                run2Cond.signal();
                System.out.println("runner 1 exit");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }

        }
    }

    static class Runner2 implements Runnable {
        @Override
        public void run() {
            lock.lock();
            try {
                System.out.println("runner 2 start");
                run2Cond.await();
                System.out.println("runner 2 exit");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }

        }
    }
    public static void main(String[] args) {
        new Thread(new Runner1(),"runner1").start();
        new Thread(new Runner2(),"runner2").start();
    }
}

輸出的結(jié)果始終是:

runner 1 start
runner 2 start
runner 1 exit
runner 2 exit

?? 我使用了工具對上面的代碼進行了調(diào)試,大致說一下流程,順便用來捋一捋等待喚醒機制。

?? 首先 runner1 啟動,獲取到鎖,打印出 “runner1 start” ,然后調(diào)用了 await 方法,此時 runner1 線程就執(zhí)行了 AQS 中的 ConditionObject 中的 await 方法,該方法首先 new 了一個新的節(jié)點,把 runner1 封裝到這個節(jié)點里面。掛在了 run1Con 的等待隊列上,然后執(zhí)行了釋放鎖并判斷中斷。緊接著 runner1 線程執(zhí)行循環(huán)體判斷是否被喚醒也就是是否在同步隊列,顯然這時候不在,就直接調(diào)用了 park 方法,執(zhí)行休眠 1 秒鐘操作, park 方法是 native 方法由操作系統(tǒng)實現(xiàn)。在上面線程釋放鎖的時候執(zhí)行的操作是 fullyRelease 這個方法調(diào)用了 release 方法,而 release 方法中釋放了鎖之后,會檢查同步隊列中是否還有以前因為沒搶到鎖而等待的線程,如果有執(zhí)行 unparkSuccessor 也就是喚醒同步隊列中的后繼線程。那么此時 runner2 會被喚醒,喚醒后就去搶鎖,獲取到 lock 鎖后輸出了 “runner2 start” ,然后 runner2 線程又會因為調(diào)用 await 處于和 runner1 同樣的境地,也就是被放入 run2Con 的等待隊列。好!此時 runner1 的超時時間到了,就會被 unpark 這個 unpark 是被操作系統(tǒng)調(diào)用的,之后繼續(xù)執(zhí)行循環(huán)體發(fā)現(xiàn)超時時間小于等于 0 ,則調(diào)用 transferAfterCancelledWait 里面調(diào)用了 enq 就是加入同步隊列,接著開始競爭鎖,開始執(zhí)行 run2Con 上的 signal 此時 signal 調(diào)用 doSignal 先執(zhí)行 do while 中的循環(huán)體,runner2 從 run2Con 的等待隊列上移除,然后執(zhí)行 transferForSignal 里面又調(diào)用了 enq 將他加入同步隊列,并返回同步隊列中的前驅(qū),前驅(qū)節(jié)點狀態(tài)不是 Cancelled 或者 可以被置為 SIGNAL 則 signal 方法結(jié)束。接著打印了 “runner1 exit” 。接著需要執(zhí)行 finally 里面的釋放鎖的操作了,顯然 unlock 肯定調(diào)用了 release ,而 release 會喚醒同步隊列中的后繼的線程,那么位于同步隊列中的 runner2 之前的 park 狀態(tài)就會被打斷,從而跳出 while 循環(huán),執(zhí)行獲取鎖的操作。打印出 “runner2 exit” ,最后釋放鎖整個程序結(jié)束。

?? 現(xiàn)在總算是吧 Condition 的等待喚醒機制弄清楚了。也把 AQS 中的兩個內(nèi)部類的功能都解釋完了。接下來就看 AQS 中的方法。

2. 重要方法

  1. get/setState
  2. release/tryRelease/unparkSuccessor/fullyRelease
  3. acquire/tryAcquire/addWaiter/tryQueued
  4. acquireShared
  5. releaseShared

?? 這些屬于 AQS 中常用的方法,但是里面的核心方法都是模板方法,也就是說由繼承他的子類來實現(xiàn),所以只能看個大概的邏輯。一會等到講 ReentrantLock 時再詳細說這里面的方法。

3. ReentrantLock 內(nèi)部類 Sync/fairSync/noFairSync

1. Sync

?? 這三個內(nèi)部類實際上是繼承自 AQS ,也就是說 ReentrantLock 是采用了 AQS 作為自己的核心并發(fā)控制組件完成的一系列的鎖操作,及等待喚醒機制。

?? 首先看一下 Sync 他是后面兩個的父類,他直接繼承自 AQS 。AQS 中留了幾個比較重要的模板方法 tryAcquire 、tryRelease 。這個方法直接實現(xiàn)了一些在公平鎖和非公平鎖中的通用操作,也就是釋放鎖的操作 tryRelease 。

?? tryRelease 的實現(xiàn)很簡單,主要就是依賴于 AQS 中的 state 屬性,如果state 值減去要釋放的信號量為 0 則釋放成功,否則失敗。

        // 釋放鎖的公共操作
        protected final boolean tryRelease(int releases) {
            // 釋放鎖首先就是使用 AQS 中的 state 的值減去信號量 判斷是否為0
            // 如果是 0 則表明成功釋放鎖,獨占線程設為 null,否則說明還占用鎖
            int c = getState() - releases;
            // 必須獲取到鎖才能解鎖,否則拋異常
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

2. fairSync

    公平鎖執(zhí)行 lock 操作就是執(zhí)行了 AQS 中的 acquire(1) 也就是請求一個鎖資源。但是注意,在 AQS 中的 acquire 中的 tryAcquire 方法沒有實現(xiàn),所以必須由當前類實現(xiàn)。

    在 tryAcquire 中做的事情就是看是否有代碼在臨界區(qū)。沒有則還要看同步隊列中是否有線程等待,當只有這一個線程在獲取鎖的時候才能正常的獲取鎖,其他情況都失敗。
// 公平鎖
    static final class FairSync extends Sync {
        final void lock() {
            acquire(1);
        }

        // 沒有代碼在臨界區(qū)或者是當前線程的重入 則獲取成功,否則失敗
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            // 如果當前線程在獲取鎖的過程沒有其他線程在臨界區(qū)
            if (c == 0) {
                // 如果同步隊列中沒有等待的線程,就設置 state ,并且當前線程設為獨占線程
                if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            // 有程序在臨界區(qū),如果是當前線程可重入,加上請求的資源數(shù)
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            // 競爭鎖失敗,因為他是公平的鎖競爭
            return false;
        }
    }

3. noFairSync

同理,這個方法也需要實現(xiàn) lock 和 tryAcquire 操作。在 lock 中直接判斷是否有代碼在臨界區(qū),沒有則直接獲取到鎖,與公平鎖不同的是:公平鎖還判斷了等待隊列中是否有等待的線程。有在臨界區(qū)的情況時執(zhí)行 acquire 操作。同樣的,首先要執(zhí)行 tryAcquire 如果失敗,加入同步隊列并自旋獲取鎖。還是 tryAcquire 的實現(xiàn),這里又調(diào)用了 nonfairTryAcquire。

    // 非公平鎖
    static final class NonfairSync extends Sync {
        final void lock() {
            // 如果沒有代碼在臨界區(qū) 直接獲取鎖,獨占
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
            // 有代碼在臨界區(qū)則執(zhí)行嘗試獲取鎖
                acquire(1);
        }

        // 和公平鎖中的 tryAcquire 一模一樣只是少了關(guān)于同步隊列中是否有等待線程的判斷
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }
    
    final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            // 沒有線程獲取鎖 直接獲取到鎖  和公平鎖中的 tryAcquire 一模一樣只是少了關(guān)于同步隊列的判斷
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            // 重入鎖
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

?? 好了,現(xiàn)在我們 AQS 中的空的核心方法也被子類實現(xiàn)了,那么現(xiàn)在 fairSync 和 noFairSync 就算是一個完整的 AQS 了。此時看一下加解鎖的流程。

只說公平鎖,因為非公平鎖就只是少了一個判斷。

  1. 首先 sync 調(diào)用 lock 方法,讓后 lock 調(diào)用了 AQS 的 acquire(1) 也就是獲取一個鎖資源。

  2. acquire 就先調(diào)用 tryAcquire(1) 嘗試獲取鎖,這時候代碼又回調(diào)到 sync 中的實現(xiàn)的 tryAcquire 方法,這個方法先判斷鎖是否已經(jīng)被別的線程使用,然后需要確定沒有更早的線程在同步隊列等待獲取鎖,才把當前線程設置為獨占線程,并設置 state 值獲取鎖。但是如果有代碼在臨界區(qū)需要判斷是否為當前線程,因為鎖是可重入的。如果是當前線程則 state 加上請求鎖的個數(shù),返回。

  3. 這時候又回到 AQS 中,如果上面嘗試獲取鎖的過程失敗,就需要調(diào)用 addWaiter 將當前線程封裝成一個獨占節(jié)點,等待狀態(tài)默認為 0,并且返回當前節(jié)點。

  4. 加入同步隊列后,再調(diào)用 acquireQueued 方法,當此線程是同步隊列中等待的第一個線程則自旋嘗試獲取鎖,畢竟很可能正在執(zhí)行的線程馬上就會釋放鎖了,再進行休眠不合適。如果自旋獲取鎖失敗則判斷節(jié)點狀態(tài)是否為 SIGNAL 然后執(zhí)行等待操作。

  5. 鎖獲取成功則把當前節(jié)點設置為頭結(jié)點,把 thread = null

  6. 至此,Acquire 方法執(zhí)行結(jié)束。

然后調(diào)用 unlock 方法解鎖操作。

  1. 解鎖操作就沒那么麻煩,首先還是調(diào)用到了 AQS 中的 release 方法,這個方法首先嘗試解鎖當前線程,又回調(diào)到了 sync 中的 tryRelease 。

  2. tryRelease 邏輯比較簡單,使用 AQS 中的 state 減去釋放的資源數(shù),等于 0 代表完全釋放,否則釋放失敗。

  3. 如果 tryRelease 成功執(zhí)行就要去喚醒同步隊列中的后繼節(jié)點,繼續(xù)執(zhí)行。

  4. 至此,release 方法執(zhí)行完畢。

4. AQS 中的要方法

1. get/setState

??這兩個方法主要是對 state 變量的 volatile 的讀寫,其實里面就就是普通的 get/set 方法。但是注意的一點就是 state 是 volatile 的。

    // 對狀態(tài)變量的 volatile 讀寫
    protected final int getState() {
        return state;
    }
    protected final void setState(int newState) {
        state = newState;
    }

2. release/tryRelease/unparkSuccessor/fullyRelease

?? 這幾個方法在一起說主要是因為他們之間存在調(diào)用鏈,首先來看 release 這個方法我們在上面也分析了,里面調(diào)用了 tryRelease 、unparkSuccessor。 也就是首先調(diào)用 tryRelease 來釋放當前線程的鎖,如果釋放成功就調(diào)用 unparkSuccessor 來喚醒同步隊列中后繼節(jié)點。其中 tryRelease 是由子類來實現(xiàn),里面的主要邏輯就是看當前的 state 變量的值在修改過后是否為0 。這里還有一個 fullRelease 主要是在 ConditionObject 中調(diào)用的,當執(zhí)行 await 的操作的時會執(zhí)行此方法釋放鎖。

 //  嘗試釋放鎖
    public final boolean release(int arg) {
        // 如果釋放鎖成功 喚醒同步隊列中的后繼節(jié)點
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
    
    // 喚醒同步隊列中的后繼節(jié)點
    private void unparkSuccessor(Node node) {
        // node 一般就是當前正在運行的線程
        int ws = node.waitStatus;
        // 當前線程置為初始狀態(tài)   可以失敗
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);
        // 找到同步隊列中的下一個節(jié)點
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {  //沒有下一個節(jié)點或者被取消
            s = null;
            // 從后往前找第一個沒有被取消的線程
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        // 喚醒那個線程
        if (s != null)
            LockSupport.unpark(s.thread);
    }
    
    final int fullyRelease(Node node) {
        boolean failed = true;
        try {
            int savedState = getState();
            if (release(savedState)) {
                failed = false;
                return savedState;
            } else {
                throw new IllegalMonitorStateException();
            }
        } finally {
            if (failed)
                node.waitStatus = Node.CANCELLED;
        }
    }

3. acquire/tryAcquire/addWaiter/acquireQueued

這個和上面的一樣,在執(zhí)行了 acquire 后,會去調(diào)用子類復寫的 tryAcquire 方法,這個方法就是看有否有代碼塊在臨界區(qū),沒有的話直接獲取鎖(非公平鎖),設置 state,有的話要判斷是不是當前線程能否進行重入操作,否則就獲取失敗。失敗后會調(diào)用 addWaiter ,new 一個新的節(jié)點加入到同步隊列,接著調(diào)用了 acquireQueued 如果這個節(jié)點是同步隊列中的第一個等待的線程(但不是第一個節(jié)點,因為第一個節(jié)點是 thread=null 的運行中的線程)就自旋一段時間看能否獲取到鎖。不能則 park 等待。

// 獲取鎖
    public final void acquire(int arg) {
        // 嘗試獲取鎖 失敗則加入同步隊列 如果是同步隊列中的第一個線程就自旋獲取鎖
        // 上面的步驟的自旋獲取鎖階段,返回的是是否需要中斷,所以下面就進行 selfInterrupt
        // tryAcquire 是模板方法,因為對于公平鎖和非公平鎖獲取鎖方式不同
        if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    
    
    // 創(chuàng)建一個節(jié)點放入到同步對列中   可傳入是否為獨占鎖   返回當前節(jié)點
    private Node addWaiter(Node mode) {
        // 默認的 status 是 0
        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;
            // 把 tail 設置為 node 成功說明沒有競爭
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        // 失敗則就說明空隊列   創(chuàng)建頭結(jié)點
        enq(node);
        return node;
    }
    
     final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            // 自旋獲取鎖
            for (;;) {
                // 獲取前驅(qū)節(jié)點
                final Node p = node.predecessor();
                // 如果前驅(qū)是空的頭結(jié)點,那么也就是說當前線程就是隊列中的第一個線程 并嘗試獲取鎖  成功的話方法返回中斷情況
                if (p == head && tryAcquire(arg)) {
                    // 把當前節(jié)點設置為頭結(jié)點  thread=null 也就可以看做當前線程在運行,所以就不在同步隊列
                    setHead(node);
                    // gc
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                // 如果獲取鎖失敗,檢測為 SIGNAL 或者設置為 SIGNAL 然后讓此線程等待 等待操作在 parkAndCheckInterrupt 中完成
                if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            // 失敗 取消
            if (failed)
                cancelAcquire(node);
        }
    }

5. 總結(jié)

?? 其實到這里 ReentrantLock 已經(jīng)講完了,因為他底層全部調(diào)用的是 Sync 中的方法,也就是全都是調(diào)用了 AQS 中的方法。而 AQS 中的大部分重要的方法都已經(jīng)看過了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,494評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,283評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,714評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,410評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,940評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,776評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,210評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,654評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內(nèi)容