JAVA REENTRANTLOCK、SEMAPHORE 的實現與 AQS 框架

ReentrantLock是JDK提供的一個可重入互斥鎖,所謂可重入就是同一個鎖允許被已經獲得該鎖的線程重新獲得。可重入鎖的好處可以在遞歸算法中使用鎖,不可重入鎖則導致無法在遞歸算法中使用鎖。因爲第二次遞歸時由於第一次遞歸已經佔有鎖,而導致死鎖。本文我們將探討JDK中ReentrantLock的實現。

Semaphore是JDK提供的一個可共享的同步組建,有n個許可,多個線程可以共同去獲得許可,當線程申請的許可小於n時即可成功申請,否則申請失敗。

AQS(AbstractQueuedSynchronizer)是Java實現同步組建的基礎框架,一般以靜態內部類的形式實現在某個同步組件類中,通過代理的方式向外提供同步服務,ReentrantLock和Semaphore都是基於AQS實現的同步組件,前者是獨佔式同步組建,即一個線程獲得後,其他線程無法獲得。後者是共享式同步組件,一個線程獲得後,在滿足的條件下,其他線程也可以獲得。

AQS工作原理

AQS是Java實現同步組建的基礎框架,其基本思想是用一個volatile int state變量來表示當前同步組件的狀態,用getState()獲取同步組件的狀態,用compareAndSet(int expect, int update)來對state狀態進行操作,compareAndSet可以保證對state變量更新值的原子性。AQS中很多方法是final的,即不允許用戶覆蓋,用戶自定義的方法一般有:

  • tryAcquire: 獨佔式獲取同步狀態,該函數一般首先查詢state的值,如果state不允許繼續被獲取,直接返回false。如果state允許繼續被獲取,CAS嘗試更新state的值,成功返回true,失敗返回false
  • tryAcquireShared:共享式的獲取同步狀態,該一般是在CAS死循環獲取state的值,計算state被獲取後的值,如果該值爲負數,直接返回負數表示失敗,如果該值爲正值,則用CAS更新該值,當CAS更新失敗時,重複上述步驟,直至返回負數或CAS更新成功返回正值。
  • tryRelease:獨佔式的釋放同步狀態
  • tryReleaseShared:共享式的釋放同步狀態,一般在CAS死循環中反覆嘗試,直至釋放成功
  • isHeldExclusively:判斷當前同步器是否被當前線程佔有

AQS提供的模板方法有:

  • acquire:獨佔式的獲取同步狀態,獲取成功則返回,獲取失敗則會進入等待隊列,該方法會調用用戶自定義的tryAcquire函數
  • acquireInterruptibly:與acquire類似,不同在於當進入等待隊列時,遇到中斷會拋出InterruptedException異常,用戶可以處理該中斷異常
  • tryAcquireNanos:在acquireInterruptibly的基礎上增加了時間限制,一定時間內沒有成功獲取則返回false
  • acquireShared:共享式的獲取同步狀態,成功則返回,失敗則進入等待隊列,該方法會調用用戶自定義的tryAcquired函數
  • acquireSharedInterruptibly:在等待隊列可以相應中斷,與上類似
  • tryAcquireShared:在acquireSharedInterruptibly增加了超時限制
  • release:獨佔式的釋放同步狀態,會調用用戶自定義tryRelease函數
  • releaseShared:共享式的釋放同步狀態,會調用用戶自定義tryReleaseShared函數
  • getQueuedThreads:獲取等待隊列線程集合

ReentrantLock源碼分析

ReentrantLock的默認構造函數是

1
2
3
public ReentrantLock() {
        sync = new NonfairSync();
}

NonfairSync繼承了Sync,Sync是一個抽象類,並繼承了抽象類AbstractQueuedSynchronizer。
ReentrantLock是一個獨佔式的鎖,所以它需要實現tryAcquire函數和tryRelease函數

tryAcquire函數源碼如下

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

nonfairTryAcquire(acquires)源碼如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;
}
  • 先得到當前線程
  • 查詢當前state值,如果爲0則說明當前鎖還未被其他線程獲取,則嘗試CAS獲得鎖,成功則把佔有鎖的線程設置爲當前線程,返回true。失敗返回false。
  • 如果state不爲0則說明該鎖已經被其他線程獲取,則檢查獲得鎖的線程是否是當前線程以實現可重入特性,如果是,則更新state的值,並返回true。此處更新不需要CAS,因爲只有當前線程可以操作state。
  • 其他情況返回false

tryRelease函數源碼如下

1
2
3
4
5
6
7
8
9
10
11
12
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;
}
  • 首先得到釋放之後的狀態值c
  • 檢查當前釋放鎖的線程,如果不是已佔有鎖的線程則拋出異常,因爲ReentrantLock是獨佔式鎖,釋放鎖的線程一定是佔有鎖的線程
  • 如果c是等於0的,說明獲取鎖的所有函數都已經返回,則鎖釋放成功
  • 如果c不等於0,說明只是部分遞歸的函數返回,部分遞歸函數還未返回,則釋放失敗,鎖依然被佔有

Lock函數源碼

1
2
3
public void lock() {
    sync.lock();
}

sunc的lock函數

1
2
3
4
5
6
final void lock() {
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
 }

該函數首先直接嘗試CAS操作,成功則設置當前函數爲佔有鎖的函數,返回,失敗則調用acquire函數。acquire函數爲AQS實現的模板方法,它嘗試獲得鎖,成功則返回,不成功則進入等待隊列直至獲取成功。

unLock函數源碼

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

調用tryRelease函數釋放鎖。

Semaphore的源碼

Semaphore構造函數如下:

1
2
3
public Semaphore(int permits) {
    sync = new NonfairSync(permits);
}

與ReentrantLock代碼結構非常相似。Semaphore是一個共享式的同步組建,它應該實現tryAcquireShared和tryReleaseShared

tryAcquireShared函數源碼:

1
2
3
protected int tryAcquireShared(int acquires) {
        return nonfairTryAcquireShared(acquires);
}

nonfairTryAcquireShared源碼:

1
2
3
4
5
6
7
8
9
final int nonfairTryAcquireShared(int acquires) {
        for (;;) {
            int available = getState();
            int remaining = available - acquires;
            if (remaining < 0 ||
                compareAndSetState(available, remaining))
                return remaining;
        }
}
  • 函數首先取得當前的可用許可數,並計算被獲取acquires個許可後剩餘的許可數。
  • 如果剩餘的許可數小於0直接返回剩餘的許可數,即負值
  • 如果大於0則嘗試使用CAS循環更新state的值,更新失敗則重試上述步驟,直至返回負值更新失敗,或者返回非負值更新成功。

tips:與獨佔式的tryAcquire邏輯不太一樣,獨佔式的tryAcquire在CAS操作失敗後,直接返回失敗。本人覺得共享式的tryAcquiredShared在CAS操作失敗後,因爲組件是共享的,所以再次嘗試獲取同步組件成功的可能性較大,所以在CAS失敗後,嘗試再次更新。而獨佔式的CAS更新失敗後,組件已經被其他線程獲取,再次嘗試成功的可能性較小,所以沒有重新嘗試。純屬個人觀點。

tryReleasedShared源碼

1
2
3
4
5
6
7
8
9
10
protected final boolean tryReleaseShared(int releases) {
        for (;;) {
            int current = getState();
            int next = current + releases;
            if (next < current) // overflow
                throw new Error("Maximum permit count exceeded");
            if (compareAndSetState(current, next))
                return true;
        }
}
  • 函數計算釋放後的state值並驗證是否溢出
  • CAS更新state的值直至成功

acquire函數源碼

1
2
3
public void acquire() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

acquireSharedInterruptibly爲AQS提供的模板方法,調用了tryAcquireShared,成功直接返回,不成功加入等待隊列,並添加了處理中斷的機制

release函數源碼

1
2
3
public void release() {
    sync.releaseShared(1);
}

releaseShared爲AQS提供的模板方法,調用了tryReleaseShared

總結

完整的ReentrantLock和Semaphore實現非常複雜,本文旨在介紹AQS框架,並通過ReentrantLock和Semaphore一個獨佔式的同步組件和一個非獨佔式的同步組件來學習怎麼使用AQS實現通組件,具體來說分爲以下步驟:

  • 待實現的同步組件是獨佔式的還是共享式的
  • 獨佔式的同步組件實現tryAcquire和tryRelease,非獨佔式的實現tryAcquireShared和tryReleaseShared
  • 將我們實現的同步組建相應的方法如Lock和unLock代理到AQS對應的函數包括用戶自定義的函數和AQS提供的模板函數

AQS的方便之處在於我們只需要實現tryAcquire和tryRelease或tryAcquireShared和tryReleaseShared就可以使用AQS幫我們實現好的阻塞的acquire函數,可中斷的acquire函數,帶超時的acquire函數等模板函數,大大簡化了用戶的開發量和難度。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章