Java併發——重入鎖ReentrantLock的實現原理及源碼解析

1、什麼是可重入鎖?

可重入鎖就是對於已經獲得鎖的線程,可以重複的多次的獲得該鎖。

而不可重入的鎖在線程獲得該鎖後,該線程如果再次請求獲得該鎖,就會在調用tryAquires()的時候返回false,從而阻塞自己。

2、可重入鎖的實現原理?

要實現可重入鎖的關鍵有兩個,一個怎麼識別當前請求鎖的線程是不是已經獲取鎖的線程,另一個因爲在一個線程重複的獲取了n次鎖以後,必須要釋放n次鎖才能完全釋放鎖,這怎麼實現?

對於第一個問題,因爲可重入鎖是獨佔鎖,所以只要比較當前線程是不是獨佔鎖的線程,如果是則可以再次加鎖,如果不是則返回false。

對於第二個問題,需要一個與鎖關聯的計數器,來對加鎖的重數進行計數,每次獲得鎖都應該讓計數器自增,而每次釋放鎖都應該讓計數器自減,當計數器爲0時,表示鎖已經成功釋放。

源碼分析:

那麼我們來看看源碼:

public class ReentrantLock implements Lock, java.io.Serializable 

▶從繼承關係上,可見ReentrantLock是實現了Lock接口,因此Lock接口的功能ReentrantLock也有Lock接口的可中斷等待鎖的線程,可以實現Condition等功能。

 private final Sync sync;
 abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;
        abstract void lock();
        //重寫nonFairAquire
         final boolean nonfairTryAcquire(int acquires) {
            //省略此處,在公平鎖和非公平鎖的實現中會講到
        }
        //重寫tryRelease
        protected final boolean tryRelease(int releases) {
            //省略此處
        }

從實現上可以看出,ReentrantLock是通過組合自定義同步器來實現鎖的獲取與釋放,在ReentrantLock自定義了內部類sync,內部類sync繼承了AbstractQueuedSynchronizor,並重寫了nonfairTryAquire()和tryRelease()方法來獲得鎖和釋放鎖。

▶然後再看ReentrantLock的構造方法:

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

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

可以看出,ReentrantLock默認實現非公平鎖,如果傳入表示公平的boolean變量fair,那麼fair爲true的時候,實現公平鎖,fair爲false的時候,實現非公平鎖。而在ReentrantLock中實現公平鎖和非公平鎖都是通過實現一個靜態內部類來實現了,該靜態內部類繼承了sync靜態內部類。源碼如下所示:

公平鎖靜態內部類

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

因爲在sync內部類中沒有重寫公平鎖的獲取鎖的方法,因此在公平鎖的內部類中重寫tryAquire()方法,實現公平鎖的獲取。

通過tryAquire()方法我們可以看出ReentrantLock的實現流程:首先獲取鎖的同步狀態值,這個值是被volatile修飾的,這樣可以保證這個值在多線程之間是可見的,如果鎖沒有被佔用(c==0)那麼讓隊列頭的線程來獲取鎖,如果鎖已經被佔用,因爲ReentrantLock是排他鎖,因此可以把當前線程和獨佔的線程比較,如果當先線程就是獨佔鎖的線程,那麼可以獲取鎖,如果不是,則獲取鎖失敗。

非公平鎖靜態內部類

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

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

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

因爲在sync內部類中已經重寫了nonFairAquire()方法,因此直接調用nonFairAquire()方法即可。

此外因爲在sync內部類中已經重寫了tryRelease()方法,因此在這兩個子類中都沒有重寫這個方法,需要的時候直接調用即可。



3、細節:

synchronized關鍵字也是一種可重入鎖,比如synchronized修飾的遞歸方法,在方法執行時,執行的線程在獲得了鎖以後仍然能夠多次的獲得鎖,而不會因爲鎖之前已經被佔有被阻塞線程本身。

重入鎖與synchronized的區別:

○性能上:

    重入鎖是顯式的重入,而synchronized是隱式的重入。也就是說重入鎖是用戶自己添加和釋放,由JDK實現,而 synchronized由操作系統來添加和釋放鎖,由JVM實現。因此便利性是synchronized優於重入鎖。

     鎖的細粒度和靈活度:重入鎖優於synchronized

     synchronized在優化前,性能遠不如重入鎖,但是在synchronized引入偏向鎖後、輕量級鎖(自旋鎖)後,二者的性能區別就不大了,在兩者都可用的情況下,官方甚至建議使用synchronized。

○功能上:

    因爲重入鎖實現了lock接口,因此提供了Condition類,可以實現分組喚醒需要喚醒的線程,而不是像synchronized一樣要麼隨機喚醒,要麼全部喚醒。

    重入鎖可以實現公平鎖,但是默認的是實現非公平鎖;而synchronized只能實現非公平鎖;(關於公平鎖與非公平鎖,參見:點擊打開鏈接

    重入鎖提供了一種能夠終端等待鎖的線程,通過lock.lockInterruptibly( )實現。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章