深刻理解 ReentrantLock

ReentrantLock

ReentrantLock 是一種可重入鎖,它指的是一個線程可以對資源重複加鎖。ReentrantLocksynchronized 相似,可以保證解決線程安全問題,可是卻提供了比 synchronized 更強大、靈活的機制,例如可中斷式的獲取鎖、可定時的獲取鎖等。java

另外,ReentrantLock 也提供了公平鎖與非公平鎖的選擇,它們之間的區別主要就是看對鎖的獲取與獲取鎖的請求的順序是不是一致的,選擇公平鎖時,等待時間最長的線程會最優先獲取到鎖,可是公平鎖獲取的效率一般比非公平鎖要低。能夠在構造方法中經過傳參的方式來具體指定選擇公平或非公平。安全

公平鎖

ReentrantLock 中,有一個抽象內部類 Sync,它繼承自 AQSReentrantLock 的大部分功能都委託給 Sync 進行實現,其內部定義了 lock() 抽象方法,默認實現了 nonfairTryAcquire() 方法,它是非公平鎖的默認實現。多線程

Sync 有兩個子類:公平鎖 FairSyncNonFairSync,實現了 Sync 中的 lock() 方法和 AQS 中的 tryAcquire() 方法。函數

NonFairSync

NonFairSynclock() 方法的實現以下:性能

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

首先,非公平鎖能夠當即嘗試獲取鎖,若是失敗的話,會調用 AQS 中的 acquire 方法,其中 acquire 方法又會調用由自定義組件實現的 tryAcquire 方法:優化

protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}
複製代碼

nonfairTryAcquire() 方法在 Sync 中已經默認實現:ui

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        // 使用 CAS 設置同步狀態
        if (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;
}
複製代碼

這裏,首先會判斷的當前線程的狀態是否爲 0,也就是該鎖是否處於空閒狀態,若是是的話則嘗試獲取鎖,設置成功將當前線程設置爲持有鎖的線程。spa

不然的話,就判斷當前線程是否爲持有鎖的線程,若是是的話,則增長同步狀態值,獲取到鎖,這裏也就驗證了鎖的可重入,再獲取了鎖以後,能夠繼續獲取鎖,只需增長同步狀態值便可。線程

FairSync

FairSynclock() 方法的實現以下:code

final void lock() {
    acquire(1);
}
複製代碼

公平鎖只能調用 AQSacquire() 方法,再去調用由自定義組件實現的 tryAcquire() 方法:

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;
}
複製代碼

這裏惟一與非公平鎖不一樣的是在獲取同步狀態時,會調用 hasQueuedPredecessors 方法,這個方法用來判斷同步隊列中是否有前驅節點。也就是當前線程前面再沒有其餘線程時,它才能夠嘗試獲取鎖。

釋放鎖

ReentrantLockunlock 方法內部調用 AQSrelease 方法釋放鎖,而其中又調用了自定義組件實現的 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;
}
複製代碼

首先,判斷當前線程是不是持有鎖的線程,若是不是會拋出異常。若是是的話,再減去同步狀態值,判斷同步狀態是否爲 0,即鎖被徹底釋放,其餘線程能夠獲取同步狀態了。

若是沒有徹底釋放,則僅使用 setState 方法設置同步狀態值。

指定公平性

ReentrantLock 的構造函數中能夠指定公平性:

  • 默認建立一個非公平的鎖
public ReentrantLock() {
    sync = new NonfairSync();
}
複製代碼
  • 建立一個指定公平性的鎖。
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}
複製代碼

synchronized 和 ReentrantLock 區別

這裏總結一下 synchronized 和 ReentrantLock 的異同,它們之間的相同點以下:

  • 均可以用於實現線程間的同步訪問;
  • 二者都是可重入鎖,即一個線程可以對資源重複加鎖;

其不一樣點以下:

  • 同步實現機制不一樣:
    • synchronized 經過 Java 對象關聯的 Monitor 監視器實現(不考慮偏向鎖、輕量級鎖);
    • ReentrantLock 經過 CASAQSLockSupport 等共同實現;
  • 可見性實現機制不一樣:
    • synchronized 依賴 JVM 內存模型保證包含共享變量的多線程內存可見性。
    • ReentrantLock 經過 ASQvolatile 類型的 state 同步狀態值保證包含共享變量的多線程內存可見性。
  • 使用方式不一樣:
    • synchronized 能夠用於修飾實例方法(鎖住實例對象)、靜態方法(鎖住類對象)、同步代碼塊(指定的鎖對象)。
    • ReentrantLock 須要顯式地調用 lock 方法,並在 finally 塊中釋放。
  • 功能豐富程度不一樣:
    • synchronized 只提供最簡單的加鎖。
    • ReentrantLock 提供定時獲取鎖、可中斷獲取鎖、Condition(提供 awaitsignal 等方法)等特性。
  • 鎖類型不一樣:
    • synchronized 只支持非公平鎖。
    • ReentrantLock 提供公平鎖和非公平鎖實現。但非公平鎖相比於公平鎖效率較高。

synchronized 優化之前,它比較重量級,其性能比 ReentrantLock 要差不少,可是自從 synchronized 引入了偏向鎖、輕量級鎖(自旋鎖)、鎖消除、鎖粗化等技術後,二者的性能就相差很少了。

通常來講,僅當須要使用 ReentrantLock 提供的其餘特性時,例如:可中斷的、可定時的、可輪詢的、公平地獲取鎖等,才考慮使用 ReentrantLock。不然應該使用 synchronized,簡單方便。

相關文章
相關標籤/搜索