乾貨,深入剖析ReentrantLock原始碼,推薦收藏

2022-11-14 12:11:06

ReentrantLock和Synchronized都是Java開發中最常用的鎖,與Synchronized這種JVM內建鎖不同的是,ReentrantLock提供了更豐富的語意。可以建立公平鎖或非公平鎖、響應中斷、超時等待、按條件喚醒等。在某些場景下,使用ReentrantLock更適合,功能更強大。

前兩篇文章,我們分析了AQS的加鎖流程、以及原始碼實現。當時我們就說了,AQS使用了模板設計模式,父類別中定義加鎖流程,子類去實現具體的加鎖邏輯。所以大部分加鎖程式碼已經在父類別AQS中實現了,導致ReentrantLock的原始碼非常簡單,一塊學習一下。

先看一下ReentrantLock怎麼使用?

1. ReentrantLock的使用

/**
 * @author 一燈架構
 * @apiNote ReentrantLock範例
 **/
public class ReentrantLockDemo {
    
    public static void main(String[] args) {
        // 1. 建立ReentrantLock物件
        ReentrantLock lock = new ReentrantLock();
        // 2. 加鎖
        lock.lock();
        try {
            // 3. 這裡執行具體的業務邏輯
        } finally {
            // 4. 釋放鎖
            lock.unlock();
        }
    }
}

可以看到ReentrantLock的使用非常簡單,呼叫lock加鎖,unlock釋放鎖,需要設定try/finally使用,保證在程式碼執行出錯的時候也能釋放鎖。

ReentrantLock也可以配合Condition條件使用,具體可以翻一下前幾篇文章中BlockingQueue的原始碼解析,那裡面有ReentrantLock的實際使用。

再看一下ReentrantLock的類結構

2. ReentrantLock類結構

// 實現Lock介面
public class ReentrantLock implements Lock {

    // 只有一個Sync同步變數
    private final Sync sync;

    // Sync繼承自AQS,主要邏輯都在這裡面
    abstract static class Sync extends AbstractQueuedSynchronizer {
    }

    // Sync的兩個子類,分別實現了公平鎖和非公平鎖
    static final class FairSync extends Sync {
    }
    static final class NonfairSync extends Sync {
    }

}

可以看出ReentrantLock的類結構非常簡單,實現了Lock介面。

類裡面有兩個靜態內部類,分別實現公平鎖和非公平鎖。

看一下Lock介面中,定義了哪些方法?

public interface Lock {

    // 加鎖
    void lock();

    // 加可中斷的鎖
    void lockInterruptibly() throws InterruptedException;

    // 嘗試加鎖
    boolean tryLock();

    // 一段時間內,嘗試加鎖
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    // 釋放鎖
    void unlock();

    // 新建條件狀態
    Condition newCondition();
}

就是一些使用鎖的常用方法。

在上篇文章中瀏覽AQS原始碼的時候,瞭解到AQS定義了一些有關具體加鎖、釋放鎖的抽象方法,留給子類去實現,再看一下有哪些抽象方法:

// 加獨佔鎖
protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}
// 釋放獨佔鎖
protected boolean tryRelease(int arg) {
    throw new UnsupportedOperationException();
}

// 加共用鎖
protected int tryAcquireShared(int arg) {
    throw new UnsupportedOperationException();
}
// 釋放共用鎖
protected boolean tryReleaseShared(int arg) {
    throw new UnsupportedOperationException();
}

// 判斷是否是當前執行緒正在持有鎖
protected boolean isHeldExclusively() {
    throw new UnsupportedOperationException();
}

由於ReentrantLock使用的是獨佔鎖,所以只需要實現獨佔鎖相關的方法就可以了。

3. ReentrantLock原始碼解析

3.1 ReentrantLock構造方法

// 預設的構造方法,使用非公平鎖
public ReentrantLock() {
    sync = new NonfairSync();
}

// 傳true,可以指定使用公平鎖
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

在建立ReentrantLock物件的時候,可以指定使用公平鎖還是非公平鎖,預設使用非公平鎖,顯然非公平鎖的效能更好。

先思考一個面試常考問題,公平鎖和非公平鎖是怎麼實現的?

3.2 非公平鎖原始碼

先看一下加鎖原始碼:

從父類別ReentrantLock的加鎖方法入口:

public class ReentrantLock implements Lock {
    // 加鎖入口方法
    public void lock() {
        // 呼叫Sync中加鎖方法
        sync.lock();
    }
}

在子類NonfairSync的加鎖方法:

// 非公平鎖
static final class NonfairSync extends Sync {

    // 加鎖
    final void lock() {
        // 1. 先嚐試加鎖(使用CAS設定state=1)
        if (compareAndSetState(0, 1))
            // 2. 加鎖成功,就把當前執行緒設定為持有鎖執行緒
            setExclusiveOwnerThread(Thread.currentThread());
        else
            // 3. 沒加鎖成功,再呼叫父類別AQS中實際的加鎖邏輯
            acquire(1);
    }
}

加鎖邏輯也很簡單,先嚐試使用CAS加鎖(也就是把state從0設定成1),加鎖成功,就把當前執行緒設定為持有鎖執行緒。

設計者很聰明,在鎖競爭不激烈的情況下,很大概率可以加鎖成功,也就不用走else中複雜的加鎖邏輯了。

如果沒有加鎖成功,還是需要走else中呼叫父類別AQS的acquire方法,而acquire又需要呼叫子類的tryAcquire方法。

呼叫鏈路就是下面這樣:

根據呼叫鏈路,實際的加鎖邏輯在Sync.nonfairTryAcquire方法裡面。

abstract static class Sync extends AbstractQueuedSynchronizer {
    // 非公平鎖的最終加鎖方法
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        // 1. 獲取同步狀態
        int c = getState();
        // 2. state=0表示無鎖,先嚐試加鎖(使用CAS設定state=1)
        if (c == 0) {
            if (compareAndSetState(0, acquires)) {
                // 3. 加鎖成功,就把當前執行緒設定為持有鎖執行緒
                setExclusiveOwnerThread(current);
                return true;
            }
            // 4. 如果當前執行緒已經持有鎖,執行可重入的邏輯
        } else if (current == getExclusiveOwnerThread()) {
            // 5. 加鎖次數+acquires
            int nextc = c + acquires;
            // 6. 超過tnt型別最大值,溢位了
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
}

再看一下釋放鎖的呼叫流程,公平鎖和非公平鎖流程是一樣的,最終都是執行Sync.tryRelease方法:

abstract static class Sync extends AbstractQueuedSynchronizer {
    // 釋放鎖
    protected final boolean tryRelease(int releases) {
        // 1. 同步狀態減去釋放鎖次數
        int c = getState() - releases;
        // 2. 校驗當前執行緒不持有鎖,就報錯
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;
        // 3. 判斷同步狀態是否等於0,無鎖後,就刪除持有鎖的執行緒
        if (c == 0) {
            free = true;
            setExclusiveOwnerThread(null);
        }
        setState(c);
        return free;
    }
}

再看一下公平鎖的原始碼

3.3 公平鎖原始碼

先看一下公平鎖的加鎖流程:

最終的加鎖方法是FairSync.tryAcquire,看一下具體邏輯:

static final class FairSync extends Sync {

    // 實現父類別的加鎖邏輯
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        // 1. 獲取同步狀態
        int c = getState();
        // 2. state=0表示無鎖,先嚐試加鎖(使用CAS設定state=1)
        if (c == 0) {
            // 3. 判斷當前執行緒是不是頭節點的下一個節點(講究先來後到)
            if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
            // 4. 如果當前執行緒已經持有鎖,執行可重入的邏輯
        } else if (current == getExclusiveOwnerThread()) {
            // 5. 加鎖次數+acquires
            int nextc = c + acquires;
            // 6. 超過tnt型別最大值,溢位了
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }

    // 判斷當前執行緒是不是頭節點的下一個節點(講究先來後到)
    public final boolean hasQueuedPredecessors() {
        Node t = tail;
        Node h = head;
        Node s;
        return h != t &&
                ((s = h.next) == null || s.thread != Thread.currentThread());
    }
}

公平鎖的釋放鎖邏輯跟非公平鎖一樣,上面已經講過。

4. 總結

看完了ReentrantLock的所有原始碼,是不是覺得ReentrantLock很簡單。

由於加鎖流程的編排工作已經在父類別AQS中實現,子類只需要實現具體的加鎖邏輯即可。

加鎖邏輯也很簡單,也就是修改同步狀態state的值和持有鎖的執行緒exclusiveOwnerThread。

我是「一燈架構」,如果本文對你有幫助,歡迎各位小夥伴點贊、評論和關注,感謝各位老鐵,我們下期見