天天看點

并發多線程之Lock及ReentrantLock源碼剖析

目錄

    • Lock及ReentrantLock提供的功能
    • Lock接口提供的方法
    • ReentrantLock的實作

在并發多線程之AQS源碼分析(上)和并發多線程之AQS源碼分析(下)已經對JUC中的AQS源碼進行了剖析梳理,AQS可以說是juc包下所有工具的基礎架構,其提供了兩種隊列:同步隊列和條件隊列。并且控制了資源的擷取和釋放,以及線程的阻塞和喚醒排程。那麼接下來就是學習一下juc包下各個工具是怎麼基于AQS來實作的,這裡先從Lock及其實作類ReentrantLock開始。關于Lock及ReentrantLock的使用再次就不再贅述。

Lock及ReentrantLock提供的功能

ReentrantLock實作了Lock接口,并提供一下特性:

  • 提供了獨占鎖的實作(類似于sychronized關鍵字的功能)
  • 可以指定公平/非公平擷取鎖。
  • 可重入。
  • 允許中斷。

簡單說明一下各個特性。

獨占鎖:

ReentrantLock提供的是獨占鎖,也就是在同一時刻隻有一個線程可以擷取到鎖執行。

公平/非公平擷取鎖:

當指定公平的擷取鎖時,那麼在多個線程擷取鎖時,當有一個線程擷取到鎖後則進入代碼塊執行,而沒有擷取到鎖時則進入同步隊列等待被喚醒,而此時又有一個線程來回去鎖時,首先是判斷目前同步隊列中是否具有等待的線程,當有等待的線程,則新來的線程直接加入同步隊列中等待被喚醒。

而指定非公平擷取鎖時,新加入的線程就沒有那麼有“素質了”,其不會判斷目前同步隊列中是否有等待的線程,而是直接嘗試擷取鎖,如果擷取到則執行代碼塊,沒有擷取到則進入同步隊列中等待被喚醒。

可重入

ReentrantLock提供的是可重入鎖,當線程擷取到鎖時,當再次擷取鎖時仍然可以擷取到,該實作是通過AQS中的state屬性來完成的,每擷取一次鎖則将state加一,沒釋放一次鎖則将state減一。當state為0後則完全釋放鎖。

允許中斷

當線程沒有擷取到鎖時進入同步隊列等待時,則線程可以被中斷,當線程被再次喚醒時,則将線程狀态修改為取消狀态,并且抛出異常,此時就需要使用trycatch進行捕獲異常。

在介紹完ReentrantLock提供的特性之後,接下來剖析一下其源碼實作。前面的AQS了解以後,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();
}
           
  1. lock()方法:争搶鎖資源,擷取到則執行,擷取不到則進入同步隊列并阻塞。
  2. lockInterruptibly():可中斷的擷取鎖。
  3. tryLock():嘗試擷取鎖,擷取到鎖則傳回true,擷取不到則傳回false。該方法不會将目前線程添加至同步隊列中。
  4. tryLock(long time, TimeUnit unit):嘗試擷取鎖,運作等待指定的time時間周期。同tryLock(),隻是允許等待指定的時間。
  5. unlock():釋放鎖。

ReentrantLock的實作

首先來看看一下ReentrantLock類的内部結構。該類中有三個靜态内部類:

abstract static class Sync extends AbstractQueuedSynchronizer
static final class NonfairSync extends Sync
static final class FairSync extends Sync
           

通過類的繼承關系可知,Sycn繼承自AbstractQueuedSynchronizer,而NonfairSync 和FairSync 均繼承自Sync,在前面的已經了解到AbstractQueuedSynchronizer是提供了基礎的架構,而具體的功能則依賴于具體的實作,在AbstractQueuedSynchronizer中提供了四個未實作的方法:

protected boolean tryAcquire(int arg)
protected boolean tryRelease(int arg)
protected int tryAcquireShared(int arg)
protected boolean tryReleaseShared(int arg)

           

而這個四個方法實作不同則提供了不同的功能,ReentrantLock是獨占鎖,是以這裡沒有重寫共享相關的方法。這裡的Sycn抽象類中定義了獨占鎖公用的方法,如資源釋放等。

而NonfairSync 和FairSync 則實作了公平鎖和非公平鎖的實作。

在了解了ReentrantLock的類結構以後,接下來從Sycn類的屬性方法說起。

Sycn

  1. 非公平擷取鎖資源。
    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;
        }
               

    該方法為非公平鎖搶占資源

    1>判斷目前state是否為0,為0則通過compareAndSetState(0, acquires)方法設 置state為,成功則将目前線程設定為獨占。

    2>State不為0則判斷獨占線程是否是目前線程,是則将state+acquires指派給state。

    3>都不滿足則傳回false。

  2. 釋放鎖資源。
    protected final boolean tryRelease(int releases) {
            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;
        }
               
  3. 擷取條件隊列。
    final ConditionObject newCondition() {
        return new ConditionObject();
    }
               
  4. lock()擷取鎖的抽象方法。

    其他方法相對來說簡單,就不一一列舉了。

FairSync

公平鎖的實作,主要就是實作資源搶占方法tryAcquire()和lock()方法。

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

嘗試搶占資源

  1. 判斷state是否為0,為0則。

    1>判斷同步隊列中否還具有隊列(公平鎖的展現)

    2>嘗試修改state的值

    如果1.2條件滿足則搶占資源成功,将獨占線程修改為目前線程,失敗則傳回false。

  2. state不為0,則判斷獨占線程是否是目前線程,是目前線程則将state修改為 state+acquires的值。
  3. 都不成功則傳回false。

公平鎖擷取鎖方法:

final void lock() {
            acquire(1);
        }
           

調用了AQS的acquire方法,在AQS的acquire方法中調用FairSync的tryAcquire實作方法進行鎖的搶占,沒有擷取到鎖則進入同步隊列中等待喚醒。

NonfairSync

非公平鎖的lock方法及tryAcquire方法。

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

可以看到NonfairSync的lock方法不是直接調用acquire方法進行鎖資源擷取的,而是直接嘗試擷取鎖。擷取失敗後才通過acquire方法進行鎖資源的擷取或者進入同步隊列。

tryAcquire實作:

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

可以看到調用了自己的nonfairTryAcquire()方法,該方法繼承自Sync,實作了非公平擷取鎖的邏輯。

在Sync、NonfairSync及FairSync的實作後,再來看一下ReentrantLock的實作。

ReentrantLock

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

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

兩個構造方法,可以看到ReentrantLock預設是非公平鎖。

關于其對于Lock接口的實作都是調用其屬性sycn的方法,就不一一列舉了。關于ReentrantLock實作的線程間的通信Condition及ConditionObject就不再進行梳理了,因為都是調用了AQS裡的方法,這些方法在前面的文章中已經梳理過了。

繼續閱讀