1. 程式人生 > 程式設計 >可重入鎖ReentrantLock原始碼閱讀

可重入鎖ReentrantLock原始碼閱讀

重入鎖ReentrantLock,顧名思義,就是支援重進入的鎖,它表示該鎖能夠支援一個執行緒對資源的重複加鎖。除此之外,該鎖的還支援獲取鎖時的公平和非公平性選擇。

閱讀這個可重入鎖類之前,可以先閱讀我的上兩篇文章,對lock以及AbstractQueuedSynchronizer這兩個類的作用和設計有一個基礎的瞭解。然後再看著個類的時候,會更好的理解。

AQS(AbstractQueuedSynchronizer)佇列同步器原始碼閱讀(一) juejin.im/post/5d67d3… AQS(AbstractQueuedSynchronizer)佇列同步器原始碼閱讀(二) juejin.im/post/5d67d3…

然後我們可以知道一般來說,ReentrantLock是一個可重入鎖,所以會實現Lock這個類,並重寫該類提供的幾個方法: //獲取鎖,釋放鎖 void lock(); void unlock(); //可相應執行緒中斷的獲取鎖,當執行緒被中斷後,鎖會被釋放。而一般的lock則不會響應 void lockInterruptibly() throws InterruptedException; //嘗試獲取鎖,獲取不到則返回false boolean tryLock(); //與上一個一樣 boolean tryLock(long time,TimeUnit unit) throws InterruptedException; //獲取對應的condition,後面會專門研究,現在只要知道,這個可以用來配合完成不同執行緒的等待/通知機制。 Condition newCondition();

然後一般重寫以上幾個方法時,需要依賴佇列同步器來獲取同步狀態,獲取不到需要加入同步佇列等等,所以一般還會設計工具類Sync繼承AbstractQueuedSynchronizer 然後根據設計不同的鎖去重寫AQS提供的一些方法。

繼承AQS的子類重寫以上AQS提供的方法後,對外ReentrantLock是實現Lock提供的幾個方法一般是呼叫Sync繼承的AQS裡面的方法。例如: 加鎖時:

//呼叫的是AQS的子類sync的lock
public void lock() {
        sync.lock();
    }
//而lock方法一般會根據具體的鎖的設計去實現我們已經重寫的acquire方法。大體的鎖的設計不會偏差太多。
複製程式碼

然後我們來具體看一下: ReentrantLock 是一個可重入鎖。 關於執行緒的排程策略分為公平排程和非公平排程。 他的設計同樣是內部有一個繼承AQS的靜態內部類Sync,同時為了區分不同的排程策略,有設計了兩個子類繼承Sync,

static final class NonfairSync extends Sync{.....}
static final class FairSync extends Sync{.....}
複製程式碼

看類名可以看出,一個是針對非公平排程策略設計的鎖,一個是公平排程的。兩種重寫同步佇列器的方法的實現不同。

再來看構造方法:

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

    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
複製程式碼

這回應該不用多解釋了吧,看類名。所以說可重入鎖是預設非公平排程策略的,就是說執行緒執行的順序是不是按執行時間順序執行的,是非公平的,效率比較高。

可重入性

可重入鎖顧名思義就是執行緒獲取到鎖之後,可以再次獲取該鎖,而不會被鎖阻塞。該鎖實現需要解決兩個問題:

1.執行緒再次獲取鎖。鎖需要去識別獲取鎖的執行緒是否為當前佔據鎖的執行緒,如果是,則再次成功獲取。

2.鎖的最終釋放。。執行緒重複n次獲取了鎖,隨後在第n次釋放該鎖後,其他執行緒能夠獲取到該鎖。

以預設的非公平排程的鎖實現來檢視獲取同步狀態時,如何處理:

 final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            //獲取當前state,重入數量
            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;
        }
複製程式碼

該方法增加了再次獲取同步狀態的處理邏輯:通過判斷當前執行緒是否為獲取鎖的執行緒來決定獲取操作是否成功,如果是獲取鎖的執行緒再次請求,則將同步狀態值進行增加並返回true,表示獲取同步狀態成功。

成功獲取鎖的執行緒再次獲取鎖,只是增加了同步狀態值,這也就要求ReentrantLock在釋放同步狀態時減少同步狀態值。

protected final boolean tryRelease(int releases) {
//釋放時減去同步狀態值
            int c = getState() - releases;
            //如果當前執行緒與持有執行緒不一致,則報物件頭狀態異常
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
          //如果同步狀態值為0,代表該鎖已全部釋放,需要釋放鎖,使其他執行緒能夠獲取該鎖
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }
複製程式碼

如果該鎖被獲取了n次,那麼前(n-1)次tryRelease(int releases)方法必須返回false,而只有同步狀態完全釋放了,才能返回true。可以看到,該方法將同步狀態是否為0作為最終釋放的條件,當同步狀態為0時,將佔有執行緒設定為null,並返回true,表示釋放成功。

###對於非公平鎖,只要CAS設定同步狀態成功,則表示當前執行緒獲取了鎖,而公平鎖則不同.

protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
//主要區別如下: 即加入了同步佇列中當前節點是否有前驅節點的判斷,如果該方法返回true,
//則表示有執行緒比當前執行緒更早地請求獲取鎖,因此需要等待前驅執行緒獲取並釋放鎖之後才能繼續獲取鎖。
                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;
複製程式碼

非公平排程策略加鎖的流程

final void lock() {

//1.如果同步操作state,獲取同步狀態成功,則設定當前執行緒為當前獨佔式獲取鎖.否則進行獲取。

//compareAndSetState(0,1)  使用的是:
//unsafe.compareAndSwapInt(this,stateOffset,expect,update);  CAS偏移地址來直接修改state的值。
            if (compareAndSetState(0,1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                // 2.否則獲取同步狀態。進去裡面看實現
               acquire(1);
        }

//1.產時獲取同步狀態,(tryAcquire),失敗則加入隊尾tail,狀態設定為EXCLUSIVE,(addWaiter(Node.EXCLUSIVE),arg))
同時進入自旋去獲取同步狀態,直到該節點前一個節點為頭節點並獲取成功,則出佇列,並喚醒下一個節點,並且響應中斷。(acquireQueued()
public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE),arg))
            selfInterrupt();
    }

tryAcquire()看上面的分析.主要呼叫我們NonfairSync重寫的nonfairTryAcquire。
如果獲取非公平的可重入鎖失敗,則執行下面方法。該方法不做具體分析了,之前已經有具體分析過了。
final boolean acquireQueued(final Node node,int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                // 1. 獲得當前節點的先驅節點
                final Node p = node.predecessor();
                // 2. 當前節點能否獲取獨佔式鎖                  
                // 2.1 如果當前節點的先驅節點是頭結點並且成功獲取同步狀態,即可以獲得獨佔式鎖
                if (p == head && tryAcquire(arg)) {
                    //佇列頭指標用指向當前節點
                    setHead(node);
                    //釋放前驅節點
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                // 2.2 獲取鎖失敗,執行緒進入等待狀態等待獲取獨佔式鎖
                if (shouldParkAfterFailedAcquire(p,node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
}
複製程式碼

非公平排程策略釋放鎖的流程就不再說了,上面有。

然後本來想要再分享下,公平排程獲取鎖和解鎖的過程,其實不用。解鎖的過程是一致的,這個前面有說了。然後加鎖的區別也是隻有一點區別。就是獲取同步狀態的時候,需要判斷前面是否有頭節點,如果沒有則可以直接獲取同步狀態,否則繼續自旋。因為公平排程是根據時間執行緒執行的時間順序獲取鎖的,所以通過控制這點,來判斷是否按時間進行獲取同步狀態,從而控制獲取鎖的順序。

從上面### 標記的那行開始看,以下程式碼的邏輯與nonfairTryAcquire基本上一直,唯一的不同在於增加了hasQueuedPredecessors的邏輯判斷,方法名就可知道該方法用來判斷當前節點在同步佇列中是否有前驅節點的判斷,如果有前驅節點說明有執行緒比當前執行緒更早的請求資源,根據公平性,當前執行緒請求資源失敗。如果當前節點沒有前驅節點的話,再才有做後面的邏輯判斷的必要性。公平鎖每次都是從同步佇列中的第一個節點獲取到鎖,而非公平性鎖則不一定,有可能剛釋放鎖的執行緒能再次獲取到鎖。

公平鎖 VS 非公平鎖

1.公平鎖每次獲取到鎖為同步佇列中的第一個節點,保證請求資源時間上的絕對順序,而非公平鎖有可能剛釋放鎖的執行緒下次繼續獲取該鎖,則有可能導致其他執行緒永遠無法獲取到鎖,造成“飢餓”現象。

2.公平鎖為了保證時間上的絕對順序,需要頻繁的上下文切換,而非公平鎖會降低一定的上下文切換,降低效能開銷。因此,ReentrantLock預設選擇的是非公平鎖,則是為了減少一部分上下文切換,保證了系統更大的吞吐量。

編寫不易,給個贊