結合ReentrantLock獲得鎖分析AQS,lock過程分析

mercury_水星發表於2020-09-29

 

此篇文章只分析了ReentrantLock物件獲得鎖的一個過程。

 

既然是結合ReentrantLock分析AQS,首先說明一下ReentrantLock的基本組成。

  • RenntrantLock中有一個靜態抽象內部類Sync,它繼承了AbstractQueuedSynchronizer也就是AQS,Sync中有一個抽象方法lock() 和 一個已經實現的方法 nonfairTryAcquire。
  • RenntrantLock中有一個成員變數sync,用於引用具體的鎖物件。
  • ReentrantLock中還有兩個靜態內部類 NonfairSync 和 FairSync ,它們都繼承了Sync類,並實現了上述的抽象方法lock() 和重寫了AbstractQueuedSynchronizer中的tryAcquire方法。
  • ReentrantLock有非公平鎖和公平鎖之分,也就是上述的NonfairSync 和 FairSync,在ReentrantLock通過父類Sync去引用子類例項(也就是上述的sync引用),而ReentrantLock中具體使用哪種鎖,取決你使用的構造方法。
  • ReentrantLock有一個狀態值state,0表示ReentrantLock物件是空閒的,大於1是被佔用的

 

分析了ReentrantLock基本組成之後,接下來就開始分析原始碼了。

 

1.生成一個非公平的ReentrantLock物件,所以後續的都是基於非公平鎖進行分析的。

    // 自定義一個物件,注意這裡使用的構造方法 
    private ReentrantLock lock = new ReentrantLock();

    // 這是ReentrantLcok中提供的兩種構造方法
    // 無引數,預設使用非公平鎖
    public ReentrantLock() {
        sync = new NonfairSync();
    }

    // 帶參,具體使用哪種鎖由自己決定
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

 

2. 確定使用了哪種鎖之後,看實際使用ReentrantLock的lock方法的過程。ReentrantLock實際是有Sync完成的,而Sync中的lock是一個抽象方法,所以具體是由其子類完成的,也就是說使用ReentrantLock的lock方法實際上就是使用的NonfairSync的lock方法。

    // ReentrantLock中的lock原始碼,這裡的sync實際指向的就是在構造方法中生成的具體鎖物件
    public void lock() {
        sync.lock();
    }

3.知道lock的呼叫流程之後,我們開始分析nonfairSync的lock方法。首先就是進行CAS操作,如果CAS成功說明此時這個執行緒獲得鎖成功了(注意文章開始部分對ReentrantLock的組成說明),直接結束lock就好,但是如果獲得鎖失敗了則進行acquire(1)操作,acquire(1)方法是由AbstractQueuedSynchronizer實現的,從這裡開始就開始涉及到AQS了(其實compareAndSetState方法也是由AQS實現的)。

final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

 4.至此,假設第一次獲得鎖失敗,我們開始分析AQS的acquire方法。下面是AQS中的原始碼,一共涉及到了4個方法,按順序逐個分析。首先是分析tryAcquire方法,你看AQS原始碼會發現這個方法其實AQS也實現了,雖然只是丟擲一個異常。因為子類對這個方法進行了重寫,Java類之間的多型性體現,這裡的tryAcquire方法實際是呼叫的子類的tryAcquire方法(還是注意文章開始的說明,非公平鎖重寫了tryAcquire方法)

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

5.於是先分析tryAcquire方法。看原始碼很簡單,就是呼叫nonfairTryAcquire方法,這個方法由靜態抽象類Sync實現,也就是NonfairSync的父類。nonfairTryAcquire的實現邏輯很簡單,主要是進行了兩個操作,一個是當ReentrantLock是空閒的時候再次競爭鎖,不是空閒的判斷此時擁有鎖的執行緒是不是自己的執行緒,是的話進行可重入操作(ReentrantLock 的可重入性就體現在這裡)。至此,如果獲得鎖成功返回true,回看第四步原始碼,acquire方法就可以結束了,但是這裡為了繼續向下分析,假設這裡還是獲得鎖失敗,返回false

    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
        // Sync
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            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;
        }

6.至此tryAcquire終於分析完了,回到第四步, 開始分析下一個方法acquireQueued(addWaiter(Node.EXCLUSIVE), arg)),因為這裡涉及到兩個方法,我們先分析裡面的addWaiter(Node.EXCLUSIVE), arg) (注:arg在整個過程中都是1)。下面addWaiter的原始碼,addWaiter的作用就是一個:將執行緒封裝成節點,追加到同步佇列的尾部,並且一定追加成功。addWaiter的方法很好看懂就是判斷此時同步佇列是不是空的,不是空的就進行一次CAS操作,將node追加到尾部,如果此時佇列為空,或者CAS失敗則就交給enq方法去實現了。Node.EXCLUSIVE表示獨佔鎖

    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;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }

7.這裡就要分析enq方法了。(分析原始碼的過程有時候是挺無聊的,尤其是一環套一環)。從下面的原始碼可以看出,enq其實有一個自旋操作,enq的主要職責就兩個,一個是當同步佇列為空的時候初始化同步佇列,還有一個是確保封裝的node一定能夠追加到同步佇列的尾部,因為這是一個自旋操作,不成功不結束啊。(自己原來有一個疑問,為什麼初始化的操作不直接在addWaiter中完成,而是放到enq之中,後來發現,初始化也是要進行CAS操作的,但是CAS不一定成功,也是需要不停的自旋的,所以將追加節點所需要自旋的操作重新封裝到enq中了(純屬個人理解,猜測。。。))

    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;
                }
            }
        }
    }

8.至此,addWaiter 分析完了,再一次回到第四步,開始分析 acquireQueued(node,1),這個方法感覺是最難理解的一個方法了。總的來說這個方法的作用判斷這個節點的前一個節點是不是頭結點,是則再次去嘗試獲得鎖,不是則進行一系列操作(詳情見shouldParkAfterFailedAcquire的原始碼),就是確保這個節點的前一個節點的狀態一定為SINGLE,這裡解釋一下SINGLE的意思,SINGLE表示當我這個節點釋放鎖或者被取消之後,我要負責通知我後面去節點退出阻塞去競爭鎖。在確保了這個節點的前一個節點是SINGLE之後,這個執行緒就會被阻塞掉,然後知道被喚醒,重新開始這裡的自旋操作。所以acquireQueued方法的作用總的說就是確保同步佇列中節點中的執行緒有機會去獲得獨佔鎖。因為這裡的自旋,可以看去它是一定會獲得鎖的,只是早晚而已。注意這裡會返回一個Interrupted值用來記錄這個執行緒是否被中斷過,以對應第四步的acquire原始碼中的selfInterrupt()操作。

    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);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

 

      至此,整個lock的過程分析完了,AQS中的主要方法也分析了一個大概,一直說ReentrantLock的lock方法是一定會獲得鎖的,為什麼這麼說,從第8步的說明可以得出結論。以上就是自己對lock過程,ReentrantLock原始碼和AQS原始碼的一個分析和總結,要說明的是,第8步分析的其實不夠完整,因為自己還沒有理解透徹,只是將自己理解的部分寫了處理,還有一個就是中斷操作,這裡也沒有分析。想要繼續瞭解我沒仔細分析部分的可以看看這個大佬的這篇部落格https://blog.csdn.net/v123411739/article/details/79304758

如有錯誤的地方,還請指正

 

 

相關文章