1. 程式人生 > >【JDK源碼分析】通過源碼徹底理解ReentrantLock顯示鎖

【JDK源碼分析】通過源碼徹底理解ReentrantLock顯示鎖

線程封裝 rac syn next 之前 線程 vat 喚醒 current

前言
ReentrantLock和synchronized一樣是一個可重入的互斥鎖,但ReentrantLock功能更強大,它提供了非公平和公平兩種鎖爭用策略供使用者選擇,而synchronized只有非公平一種。ReentrantLock提供了可中斷的鎖等待機制以及可用於多組線程需要分組喚醒的條件。

類圖
下面是ReentrantLock的類圖,內部抽象類Sync繼承了AbstractQueuedSynchronizer(以下簡稱AQS),公平鎖FairSync、非公平鎖NonfairSync繼承了抽象類Sync。
ReentrantLock

源碼
ReentrantLock類屬性和構造器
先看ReentrantLock的屬性,屬性只有sync,可見由它實現了整個類的功能。公平鎖是所有線程都按FIFO的方式進行,獲取鎖釋放鎖的順序進行;而非公平鎖則是在鎖釋放的時候,不會限制新來的線程進行鎖的爭用。

復制代碼
private final Sync sync;

//默認構造器,生成的是公平鎖
public ReentrantLock() {
    sync = new NonfairSync();
}
// 有參構造器,形參fair為true則構造公平鎖
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}   

復制代碼
ReentrantLock類主要方法
下面以ReentrantLock的主要方法的調用過程來一步步分析源碼

  1. lock 方法
    lock方法調用了內部類Sync的lock方法

    public void lock() {
    sync.lock();
    }
    lock方法為抽象方法

    abstract void lock();

    我們先看lock方法的非公平實現

1.1 lock方法的非公平實現
lock方法大量使用了其祖父類AQS中的方法,這裏補充幾點:

AQS有個state變量,用於表示鎖狀態,為0表示處理無鎖狀態,>0表示處理有鎖狀態;
compareAndSetState為AQS提供操作state變量的CAS原子方法;
關於詳細的AQS的源碼理解,可以查看本人上一篇博客,本文將不再贅述AQS中的源碼分析。
final void lock() {
// 原子操作state變量,當state為0時將其置為1

if (compareAndSetState(0, 1))
// 成功將其置為1表示
// 將當前線程設置為獨占狀態
setExclusiveOwnerThread(Thread.currentThread());
else
// CAS操作state變量失敗,表示出於有鎖狀態
acquire(1);
}
acquire方法為AQS的方法,但其調用的tryAcquire則由NonfairSync實現

public final void acquire(int arg) {
    //當tryAcquire返回false時,執行acquireQueued方法,它是將當前線程加入到隊列中等待鎖釋放
    // acquireQueued在AQS中實現,主要邏輯是將當前線程封裝成一個對象,加入到同步隊列等待鎖釋放,然後再爭用鎖
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

tryAcquire方法,獲取鎖的方法

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

    final boolean nonfairTryAcquire(int acquires) {
        // 獲取當前鎖狀態
        int c = getState();
        // 為0表示出於無鎖
        if (c == 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;
        }
        // 出於有鎖狀態時,直接返回false,表示該線程獲取鎖失敗
        return false;
    }

1.2 lock方法的公平實現
現在來看公平鎖的lock方法

    final void lock() {
        // 還是調用的祖父類的acquire方法
        acquire(1);
    }

祖父類的acquire調用的公平鎖的tryAcquire實現,細心的人可能一眼就會發現和非公平鎖的獲取鎖實現的唯一區別就在獲取鎖之前判斷之前是否已經線程在等待鎖釋放

    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
             // hasQueuedPredecessors 用來判斷是否有其它線程在等待鎖釋放
             // hasQueuedPredecessors 為AQS 實現,就是判斷同步隊列裏是否有其它線程在等待
            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;
    }****

至此lock方法的公平和非公平實現已完畢。

  1. unlock 方法
    unlock方法是二種策略鎖共用的,它也是通過調用的AQS的release方法完成鎖釋放的

    public void unlock() {
    sync.release(1);
    }
    其它方法比較簡單,這裏就不細說了。

總結
ReentrantLock一般的使用場景是synchronized不能滿足我們的功能需求時才使用;
一般在使用的時候我們使用非公平鎖,公平鎖比非公平鎖的吞吐量明顯要低很多,這是因為公平鎖在新的線程過來的時候都要去檢查同步隊列是否有等待鎖的線程。

【JDK源碼分析】通過源碼徹底理解ReentrantLock顯示鎖