天天看點

Java中的鎖之重入鎖:ReentrantLock

Java中的鎖之重入鎖:ReentrantLock

一.什麼是重入鎖

顧名思義,就是支援重進入的鎖,它表示該鎖能夠支援一個線程對資源的重複加鎖。synchronized是我們熟知的一個重入鎖;synchronized關鍵字隐式的支援重進入,比如一個synchronized修飾的遞歸方法,在方法執行時,執行線程在擷取了鎖之後仍能連續多次地獲得該鎖。

在這裡,我們引用究竟什麼是可重入鎖?文章中的例子,來示範一下:

public class ReentrantTest implements Runnable {

    public synchronized void get() {
        System.out.println(Thread.currentThread().getName());
        set();
    }

    public synchronized void set() {
        System.out.println(Thread.currentThread().getName());
    }

    @Override
    public void run() {
        get();
    }

    public static void main(String[] args) {
        ReentrantTest rt = new ReentrantTest();
        for(;;){
            new Thread(rt).start();
        }
    }

}

           
Java中的鎖之重入鎖:ReentrantLock

在這段代碼中,我們首先在run()方法中調用get(),又在get()方法中調用set(),達到了一個嵌套調用.運作代碼之後,我們發現這段代碼并沒有發生死鎖的現象,是以我們得到一個結論:synchronized是重入鎖;關于其他例子,大家可以參考上面的部落格。

二.ReentrantLock的簡介

Java中的鎖之重入鎖:ReentrantLock

首先我們來看官方文檔

Java中的鎖之重入鎖:ReentrantLock

ReentrantLock是具有與使用synchronized方法和語句通路的隐式監視鎖相同的基本行為和語義的可重入互斥的鎖,但具有擴充功能。

ReentrantLock雖然沒能像synchronized關鍵字一樣支援隐式的重進入,但是在調用lock()方法時,已經擷取到鎖的線程,能夠再次調用lock()方法擷取鎖而不被阻塞。這裡還有一個鎖擷取的公平性問題,如果在絕對時間上,先對鎖進行擷取的請求一定先被滿足,那麼這個鎖是公平的,反之,是不公平的。公平的擷取鎖,也就是等待時間最長的線程最優先擷取鎖,也可以說鎖擷取是順序的。ReentrantLock提供了一個構造函數,能夠控制鎖是否是公平的。事實上,公平的鎖機制往往沒有非公平的效率高,但是,并不是任何場景都是以TPS作為唯一的名額,公平鎖能夠減少“饑餓”發生的機率,等待越久的請求越是能夠得到優先滿足。

三.ReentrantLock的實作原理

下面将着重分析ReentrantLock是如何實作重進入和公平性擷取鎖的特性,并通過測試來驗證公平性擷取鎖對性能的影響。

3.1實作重進入

重進入是指任意線程在擷取到鎖之後能夠再次擷取該鎖而不會被鎖所阻塞,該特性的實作需要解決以下兩個問題。

  • 線程再次擷取鎖。鎖需要去識别擷取鎖的線程是否為目前占據鎖的線程,如果是,則再次成功擷取。
  • 鎖的最終釋放。線程重複n次擷取了鎖,随後在第n次釋放該鎖後,其他線程能夠擷取到該鎖。鎖的最終釋放要求鎖對于擷取進行計數自增,計數表示目前鎖被重複擷取的次數,而鎖被釋放時,計數自減,當計數等于0時表示鎖已經成功釋放。

ReentrantLock是通過組合自定義同步器來實作鎖的擷取與釋放,以非公平性(預設的)實作為例,擷取同步狀态的代碼如代碼清單3-1所示。

代碼清單3-1 ReentrantLock的nonfairTryAcquire方法
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;
        }
           

我們可以看到,該方法增加了再次擷取同步狀态的處理邏輯:通過判斷目前線程是否為擷取鎖的線程來決定擷取操作是否成功,如果是擷取鎖的線程再次請求,則将同步狀态值進行增加并傳回true,表示擷取同步狀态成功。成功擷取鎖的線程再次擷取鎖,隻是增加了同步狀态值,這也就要求ReentrantLock在釋放同步狀态時減少同步狀态值,該方法的代碼如代碼清單3-2所示。

代碼清單3-2 ReentrantLock的tryRelease方法
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;
}
           

如果該鎖被擷取了n次,那麼前(n-1)次tryRelease(int releases)方法必須傳回false,而隻有同步狀态完全釋放了,才能傳回true。可以看到,該方法将同步狀态是否為0作為最終釋放的條件,當同步狀态為0時,将占有線程設定為null,并傳回true,表示釋放成功。

3.2公平與非公平擷取鎖的差別

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