面試官: 有了解過ReentrantLock的底層實現嗎?說說看

Java架構師 發表於 2022-06-17
面試

我們可以瞭解到它是一個可重入鎖,下面我們就一起看一下它的底層實現~

建構函式

我們在使用的時候,都是先new它,所以我們先看下它的建構函式,它主要有兩個:

public ReentrantLock() {
    sync = new NonfairSync();
}

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

從字面上看,它們之間的不同點在於fair,翻譯過來就是公平的意思,大體可以猜到它是用來構建公平鎖和非公平鎖,在繼續往下看原始碼之前,先給大家科普一下這兩種鎖。

公平鎖 & 非公平鎖

  • 公平鎖 多個執行緒按照申請鎖的順序去獲得鎖,執行緒會直接進入佇列去排隊,永遠都是佇列的第一位才能得到鎖。(例如銀行辦業務取號)

這種鎖的優點很明顯,每個執行緒都能夠獲取資源,缺點也很明顯,如果某個執行緒阻塞了,其它執行緒也會阻塞,然而cpu喚醒開銷很大,之前也給大家講過

  • 非公平鎖 多個執行緒都去嘗試獲取鎖,獲取不到就進入等待佇列,cpu也不用去喚醒

優缺點正好和上邊相反,優點減少開銷,缺點也很明顯,可能會導致一直獲取不到鎖或長時間獲取不到鎖

好,有了基本概念之後,我們繼續往下看

NonfairSync

首先,我們看下非公平鎖,預設情況下,我們申請的都是非公平鎖,也就是new ReentrantLock(),我們接著看原始碼

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

    /**
        * Performs lock.  Try immediate barge, backing up to normal
        * acquire on failure.
        */
    final void lock() {
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }

    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

它繼承了Sync,Sync是一個內容靜態抽象類:

abstract static class Sync extends AbstractQueuedSynchronizer {...}

分為公平和非公平,使用AQS狀態來表示持鎖的次數,在建構函式初始化的時候都有sync = ...,我們接著看NonfairSync。在使用的時候,我們呼叫了lock.lock()方法,它是ReentrantLock的一個例項方法

 // 獲取鎖
 public void lock() {
        sync.lock();
    }

實際上內部還是調了sync的內部方法,因為我們申請的是非公平鎖,所以我們看NonfairSync下的lock實現:

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

compareAndSetState這個方法,是AQS的內部方法,意思是如果當前狀態值等於預期值,則自動將同步狀態設定為給定的更新值。此操作具有volatile讀寫的記憶體語義。

protected final boolean compareAndSetState(int expect, int update) {
    // See below for intrinsics setup to support this
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

可以看到執行lock方法,會通過AQS機制計數,setExclusiveOwnerThread設定執行緒獨佔訪問許可權,它是AbstractOwnableSynchronizer的一個內部方法,子類通過使用它來管理執行緒獨佔

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {}

可以看到它是繼承了AbstractOwnableSynchronizer。下面接著看,我們說如果實際值等於期望值會執行上邊的方法,不期望的時候會執行acquire(1)

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

這個方法以獨佔模式獲取,忽略中斷,它會嘗試呼叫tryAcquire,成功會返回,不成功進入執行緒排隊,可以重複阻塞和解除阻塞。看下AQS 內部的這個方法

protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}

我們可以看到實現肯定不在這,它的具體實現在NonfairSync

protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }

可以看到它呼叫了,nonfairTryAcquire方法,這個方法是不公平的tryLock,具體實現在Sync內部,這裡我們要重點關注一下

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();

    // 返回同步狀態值,它是AQS內部的一個方法 
    //  private volatile int state;
    // protected final int getState() {
    //     return state;
    // }
    int c = getState();
    if (c == 0) {
        // 為0就比較一下,如果與期望值相同就設定為獨佔執行緒,說明鎖已經拿到了
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 否則 判斷如果當前執行緒已經是被設定獨佔執行緒了
    else if (current == getExclusiveOwnerThread()) {

        // 設定當前執行緒狀態值 + 1 並返回成功
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    // 否則返回失敗 沒拿到鎖
    return false;
}

好,我們再回過頭看下 acquire

 public final void acquire(int arg) {
         // 如果當前執行緒沒有獲取到鎖 並且 在佇列中的執行緒嘗試不斷拿鎖如果被打斷了會返回true, 就會呼叫 selfInterrupt   
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

selfInterrupt很好理解,執行緒中斷

 static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }

其實我們關注的重點是這個方法acquireQueued,首先關注一下入參,它內部傳入了一個addWaiter,最後它回NODE節點

 private Node addWaiter(Node mode) {
       // mode 沒啥好說的就是一個標記,用於標記獨佔模式   static final Node EXCLUSIVE = null;
        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;
    }

我們可以大體從猜到,Node是一個等待佇列的節點類,是一個連結串列結構,之前我們講FutureTask原始碼的時候也遇到過這種結構,它通常用於自旋鎖,在這個地方,它是用於阻塞同步器

     +------+  prev +-----+       +-----+
head |      | <---- |     | <---- |     |  tail
     +------+       +-----+       +-----+

好,下面我們關注一下 acquireQueued

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        // 預設是 false
        boolean interrupted = false;
        // 進入阻塞迴圈遍歷 執行緒佇列
        for (;;) {
            // 返回前一個節點
            final Node p = node.predecessor();

            // 判斷如果前一個節點是頭部節點,並且拿到鎖了,就會設定當前節點為頭部節點
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                // 這裡可以看到註釋 help gc ,
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            // 檢查並更新未能獲取的節點的狀態。如果執行緒應該阻塞,則返回 true 並且執行緒中斷了
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        // 如果失敗  取消正在嘗試獲取的節點
        if (failed)
            cancelAcquire(node);
    }
}

從上面的原始碼來看,在體會一下上面講的非公平鎖的概念,是不是更好理解一些,然後就是釋放鎖unlock,這個方法我們可以看到是ReentrantLock下的一個例項方法,所以公平鎖的釋放鎖也是調的這個方法,其實最終可以猜到呼叫的還是sync的方法

public void unlock() {
        sync.release(1);
    }

Sync繼承AQS,release是AQS的內部方法

 public final boolean release(int arg) {
       // 嘗試釋放鎖  tryRelease 在Sync內部
        if (tryRelease(arg)) {
            Node h = head;
            // 如果節點存在 並且狀態值不為0
            if (h != null && h.waitStatus != 0)
                // 喚醒下個節點
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

private void unparkSuccessor(Node node) {
     
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            // 可以看到呼叫了 LockSupport來喚醒
            LockSupport.unpark(s.thread);
    }

我們再看下tryRelease, 同樣這個實現在Sync內

 protected final boolean tryRelease(int releases) {
            // 同樣釋放鎖的時候 依然使用 AQS計數
            int c = getState() - releases;
            // 判斷當前執行緒是否是獨佔執行緒,不是丟擲異常
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            // 如果是0 表示是釋放成功
            if (c == 0) {
                free = true;
                // 並且把獨佔執行緒設為null
                setExclusiveOwnerThread(null);
            }
            // 更新狀態值
            setState(c);
            return free;
        }

FairSync

公平鎖FairSync的區別在於,它的獲取鎖的實現在它的內部,Sync預設內部實現了非公平鎖

static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        // 這個方法最終呼叫 tryAcquire
        final void lock() {
            acquire(1);
        }

        // 公平鎖的實現
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            // 這邊和非公平鎖的實現有些相似 同樣判斷狀態
            if (c == 0) {
                // 判斷排隊佇列是否存在, 不存在並且比較期望值
                if (!hasQueuedPredecessors() &&
                    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");
                setState(nextc);
                return true;
            }
            return false;
        }
    }

它的實現比較簡單,通過實現可以發現,它按照申請鎖的順序來獲取鎖,排第一的先拿到鎖,在結合上面的概念理解一下,就很好理解了.

釋放鎖unlock,上面我們已經講過了~

結束語

本節內容可能有點多,主要是看原始碼,可以打斷點自己調一下, 舉一反三,通過原始碼去理解一下什麼是公平鎖和非公平鎖, ReentrantLock可重入鎖體驗在哪裡。