1. 程式人生 > >JUC之JDK自帶鎖ReentrantLock

JUC之JDK自帶鎖ReentrantLock

一、初識

ReentrantLock
出身自Java 1.5,中文名可重入鎖
是Java JDK自帶獨佔鎖的唯一實現,也是最常用的鎖,是synchronized的升級版。

1. 我們中間有個synchronized

我們已經認識過synchronized了,知道她能幫我們實現執行緒同步提供原子性語義,同時又有可重入性。同時我們也已經知道了可重入性是什麼意思,也知道公平性的含義。

當然,我們很JRE如何實現synchronized,實現它的可重入性。
但我們可以通過閱讀ReentrantLock的原始碼,來加深對sychronized一些特性的理解。

2. 升級版,升了什麼

前面說了ReentrantLock是sychronized的升級版,那麼ReentrantLock升級了什麼,為我們帶哪些新特性呢?

  1. tryLock 嘗試獲取鎖,直接穿透(無視)公平性
  2. isLocked 當前鎖是不是被持有(含自己和他人),用來監控系統(鎖)狀態
  3. hasQueuedThreas(has) 提供更多監控,這一系列含has方法都是實現狀態監控
  4. Fair And Non-Fair Model ReentrankLock提供公平與非公平兩個模式
  5. Condition 在Lock內用Condition代替Synchronized的Object監控

這些都是非常好用,非常實用的功能,而synchronized卻沒有的特徵。
還有還有,ReentrankLock還提供了一個可中斷的方法。

3. Condition

當把ReentrantLock當成synchronized時,你需要把Condition當成Object監控。功能和用法都一樣,只是名字不同而已。

專案 等待 喚醒 喚醒所有 用法
Object wait notify notifyAll 在synchronized語塊內
Condition await signal signalAll 在Lock語塊內

二、你好!ReentrankLock

我們已經認識過公平性了,我們知道她的語義是是否先到先得。那麼她怎麼實現的呢,我們好像還沒看過。接下來我們將通過閱讀ReentrantLock的原始碼,來看看她是怎麼實現公平性的。先貼程式碼吧

1. 公平

// ReentranLock$FiarSync
final void lock() {
    acquire(1);
}
public final void acquire(int arg) {
 if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
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;
}

你應該從一小段程式碼裡看出三個東西,
1. 公平
2. 可重入
3. 獨佔式

JDK在實現鎖的時候通常用’0’和’大於0`表示鎖狀態,即沒鎖上和已上鎖。

從這一小段原始碼裡面可以看到
- ReentrantLock先自己嘗試去獲取鎖,即是檢查state的狀態碼
1. 對於state為0(即鎖沒被持有的時候),且前面沒人在排隊,理論上對這種情況特別簡單直接上鎖就可以了,如鎖操作是執行緒安全的。然後並沒有,我們之前在整理AQS框架的時候已經瞭解了AQS是通過CAS實現同步,即通過一組原子性的操作完成的。

  1. 當state不為0時,她就會去判斷是誰持有,如果是自己的話,依然可以再次獲得,這就是可重入性。同時state自增,此時可以反映兩個問題,state還代表遞迴層級,最多能支援Integer.MAX_VALUE層。

    • ReentrantLock在嘗試失敗之後,將進入等待佇列繼續等待,就是阻塞

2. 不公平

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

public final void acquire(int arg) {
 if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

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

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) { // #1
            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;
}

乍一看,好像沒啥差。
只不過一來就嘗試通過CAS修改條件來獲取獨佔鎖。其它,好像沒啥了。對了,還改了個不要臉的名字叫nonfairTryAcquire

細心的你應該還能看到#1語句裡少了條件!hasQueuedPredecessors(),即是不再管前面有沒有人在排隊,就伸手要去取鎖。正因為這兩個“不道德”的插隊操作完成了非公開性,從而獲到更高的OPS。

ReentrankLock預設實現就是非公平的,因為它能有更高的吞吐量。

三、再見AQS框架

在整理AQS框架的時候,我們說AQS框架提供一個基於FIFO等待佇列,可以用於構建鎖或者其他同步裝置的基礎框架。意在能夠成為實現大部分同步需求的基礎,她用法可以參考ReentrantLock的實現。

這裡再多說幾句,ReentrantLock是實現Lock介面、同時也實現Serializable。Serializable是說它的狀態可以被系列,而Lock提供Lock的相關語義和操作。到這裡為止,好像跟AQS沒什麼關係。對對對您說得對,沒錯沒錯是這樣。不過我們說Lock是通過AQS實現同步的。因此,ReentrantLock實際上是通過一個承繼AQS的內部類——同步器(Sync),實現同步,從而完全Lock功能的。

再看看ReentrantLock提供兩種Sync的實現完全公平性語義,即FairSyncNonFairSync兩個類。而Lock的功能完全Forwarding到Sync,由Sync具體實現。所以說AQS可以用來實現同步鎖。

四、CU! ReentrantLock

  • ReentrantLock是一個獨佔鎖,可重入。
  • ReentrantLock可實現公平和非公平,通過她的構造器的引數設定
  • ReentrantLock是synchronized的升級版,可代替synchonized,更好用、更高效能