ReentrantLock 實現原理

ReentrantLock 簡介

  1. ReentrantLock,可重入鎖,是一種遞歸無阻塞的同步機制。它能夠等同於 synchronized 的使用,可是 ReentrantLock 提供了比 synchronized 更強大、靈活的鎖機制,能夠減小死鎖發生的機率。java

  2. 一個可重入的互斥鎖定 Lock,它具備與使用 synchronized 方法和語句所訪問的隱式監視器鎖定相同的一些基本行爲和語義,但功能更強大。c#

  3. ReentrantLock 將由最近成功得到鎖定,而且尚未釋放該鎖定的線程所擁有。安全

  4. 當鎖定沒有被另外一個線程所擁有時,調用 lock 的線程將成功獲取該鎖定並返回。多線程

  5. 若是當前線程已經擁有該鎖定,此方法將當即返回。可使用 #isHeldByCurrentThread() 和 #getHoldCount() 方法來檢查此狀況是否發生。ide

  6. ReentrantLock 還提供了公平鎖和非公平鎖的選擇,經過構造方法接受一個可選的 fair 參數(默認非公平鎖):當設置爲 true 時,表示公平鎖;不然爲非公平鎖。性能

  7. 公平鎖與非公平鎖的區別在於,公平鎖的鎖獲取是有順序的。可是公平鎖的效率每每沒有非公平鎖的效率高,在許多線程訪問的狀況下,公平鎖表現出較低的吞吐量。ui

ReentrantLock 總體結構以下圖:this

ReentrantLock 實現 Lock 接口,基於內部的 Sync 實現。
Sync 繼承於 AQS ,提供了 FairSync 和 NonFairSync 兩種實現。
複製代碼

Sync 抽象類

Sync 是 ReentrantLock 的內部靜態類,實現 AbstractQueuedSynchronizer 抽象類,同步器抽象類。它使用 AQS 的 state 字段,來表示當前鎖的持有數量,從而實現可重入的特性。spa

lock

/** * Performs {@link Lock#lock}. The main reason for subclassing * is to allow fast path for nonfair version. */
abstract void lock();
複製代碼

執行鎖。抽象了該方法的緣由是,容許子類實現快速得到非公平鎖的邏輯。線程

nonfairTryAcquire

nonfairTryAcquire(int acquires) 方法,非公平鎖的方式得到鎖。代碼以下:

final boolean nonfairTryAcquire(int acquires) {
    //當前線程
    final Thread current = Thread.currentThread();
    //獲取同步狀態
    int c = getState();
    //state == 0,表示沒有該鎖處於空閒狀態
    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;
}
複製代碼

該方法主要邏輯:

  1. 首先判斷同步狀態 state == 0 ?
  2. 若是是,表示該鎖尚未被線程持有,直接經過CAS獲取同步狀態。
  3. 若是成功,返回 true 。
  4. 不然,返回 false 。
  5. 若是不是,則判斷當前線程是否爲獲取鎖的線程?
  6. 若是是,則獲取鎖,成功返回 true 。成功獲取鎖的線程,再次獲取鎖,這是增長了同步狀態 state 。經過這裏的實現,咱們能夠看到上面提到的 「它使用 AQS 的 state 字段,來表示當前鎖的持有數量,從而實現可重入的特性」。
  7. 不然,返回 false 。 理論來講,這個方法應該在子類 FairSync 中實現,可是爲何會在這裏呢?在下面的 ReentrantLock.tryLock() 中,詳細解析。

tryRelease

tryRelease(int releases) 實現方法,釋放鎖。代碼以下:

protected final boolean tryRelease(int releases) {
    // 減掉releases
    int c = getState() - releases;
    // 若是釋放的不是持有鎖的線程,拋出異常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    // state == 0 表示已經釋放徹底了,其餘線程能夠獲取同步狀態了
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}
複製代碼
  1. 經過判斷當前線程是否爲得到到鎖的線程,保證該方法線程安全。
  2. 只有當同步狀態完全釋放後,該方法纔會返回 true 。當 state == 0 時,則將鎖持有線程設置爲 null ,free= true,表示釋放成功。

其餘實現方法

其餘實現方法比較簡單

// 是否當前線程獨佔
@Override
protected final boolean isHeldExclusively() {
    // While we must in general read state before owner,
    // we don't need to do so to check if current thread is owner
    return getExclusiveOwnerThread() == Thread.currentThread();
}

// 新生成條件
final ConditionObject newCondition() {
    return new ConditionObject();
}

// Methods relayed from outer class

// 得到佔用同步狀態的線程
final Thread getOwner() {
    return getState() == 0 ? null : getExclusiveOwnerThread();
}

// 得到當前線程持有鎖的數量
final int getHoldCount() {
    return isHeldExclusively() ? getState() : 0;
}

// 是否被鎖定
final boolean isLocked() {
    return getState() != 0;
}

/** * Reconstitutes the instance from a stream (that is, deserializes it). * 自定義反序列化邏輯 */
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
    s.defaultReadObject();
    setState(0); // reset to unlocked state
}
複製代碼

從這些方法中,咱們能夠看到,ReentrantLock 是獨佔獲取同步狀態的模式。

Sync 實現類

NonfairSync

NonfairSync 是 ReentrantLock 的內部靜態類,實現 Sync 抽象類,非公平鎖實現類。

lock

lock() 實現方法,首先基於 AQS state 進行 CAS 操做,將 0 => 1 。若成功,則獲取鎖成功。若失敗,執行 AQS 的正常的同步狀態獲取邏輯。代碼以下:

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

優先基於 AQS state 進行 CAS 操做,已經能體現出非公平鎖的特色。

由於,此時有可能有 N + 1 個線程正在得到鎖,其中 1個線程已經得到到鎖,釋放的瞬間,剛好被新的線程搶奪到,而不是排隊的 N 個線程。

tryAcquire

tryAcquire(int acquires) 實現方法,非公平的方式,得到同步狀態。代碼以下:

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

直接調用 #nonfairTryAcquire(int acquires) 方法,非公平鎖的方式得到鎖。

FairSync

FairSync 是 ReentrantLock 的內部靜態類,實現 Sync 抽象類,公平鎖實現類。

lock

lock() 實現方法,代碼以下:

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

直接執行 AQS 的正常的同步狀態獲取邏輯。

tryAcquire

tryAcquire(int acquires) 實現方法,公平的方式,得到同步狀態。代碼以下:

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (!hasQueuedPredecessors() && // <1>
                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() 方法,是否有前序節點,即本身不是首個等待獲取同步狀態的節點。代碼以下:

// AbstractQueuedSynchronizer.java
public final boolean hasQueuedPredecessors() {
    Node t = tail;  //尾節點
    Node h = head;  //頭節點
    Node s;

    //頭節點 != 尾節點
    //同步隊列第一個節點不爲null
    //當前線程是同步隊列第一個節點
    return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
}
複製代碼

該方法主要作一件事情:主要是判斷當前線程是否位於 CLH 同步隊列中的第一個。若是是則返回 true ,不然返回 false 。

Lock 接口

java.util.concurrent.locks.Lock 接口,定義方法以下:

void lock();

void lockInterruptibly() throws InterruptedException;

boolean tryLock();

boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

void unlock();

Condition newCondition();
複製代碼

方法解釋

ReentrantLock

java.util.concurrent.locks.ReentrantLock ,實現 Lock 接口,重入鎖。

ReentrantLock 的實現方法,基本是對 Sync 的調用。

構造方法

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

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

基於 fair 參數,建立 FairSync 仍是 NonfairSync 對象。

lock

@Override
public void lock() {
    sync.lock();
}
複製代碼

lockInterruptibly

@Override
public void lockInterruptibly() throws InterruptedException {
    sync.acquireInterruptibly(1);
}
複製代碼

tryLock

/** * Acquires the lock only if it is not held by another thread at the time * of invocation. * * <p>Acquires the lock if it is not held by another thread and * returns immediately with the value {@code true}, setting the * lock hold count to one. Even when this lock has been set to use a * fair ordering policy, a call to {@code tryLock()} <em>will</em> * immediately acquire the lock if it is available, whether or not * other threads are currently waiting for the lock. * This &quot;barging&quot; behavior can be useful in certain * circumstances, even though it breaks fairness. If you want to honor * the fairness setting for this lock, then use * {@link #tryLock(long, TimeUnit) tryLock(0, TimeUnit.SECONDS) } * which is almost equivalent (it also detects interruption). * * <p>If the current thread already holds this lock then the hold * count is incremented by one and the method returns {@code true}. * * <p>If the lock is held by another thread then this method will return * immediately with the value {@code false}. * * @return {@code true} if the lock was free and was acquired by the * current thread, or the lock was already held by the current * thread; and {@code false} otherwise */
@Override
public boolean tryLock() {
    return sync.nonfairTryAcquire(1);
}
複製代碼

tryLock() 實現方法,在實現時,但願能快速的得到是否可以得到到鎖,所以即便在設置爲 fair = true ( 使用公平鎖 ),依然調用 Sync#nonfairTryAcquire(int acquires) 方法。

若是真的但願 #tryLock() 仍是按照是否公平鎖的方式來,能夠調用 #tryLock(0, TimeUnit) 方法來實現。

tryLock

@Override
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
    return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
複製代碼

unlock

@Override
public void unlock() {
    sync.release(1);
}
複製代碼

newCondition

@Override
public Condition newCondition() {
    return sync.newCondition();
}
複製代碼

其餘實現方法

public int getHoldCount() {
    return sync.getHoldCount();
}
public boolean isHeldByCurrentThread() {
    return sync.isHeldExclusively();
}
public boolean isLocked() {
    return sync.isLocked();
}


public final boolean isFair() {
    return sync instanceof FairSync;
}

protected Thread getOwner() {
    return sync.getOwner();
}
public final boolean hasQueuedThreads() {
    return sync.hasQueuedThreads();
}
public final boolean hasQueuedThread(Thread thread) {
    return sync.isQueued(thread);
}
public final int getQueueLength() {
    return sync.getQueueLength();
}
protected Collection<Thread> getQueuedThreads() {
    return sync.getQueuedThreads();
}

public boolean hasWaiters(Condition condition) {
    if (condition == null)
        throw new NullPointerException();
    if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
        throw new IllegalArgumentException("not owner");
    return sync.hasWaiters((AbstractQueuedSynchronizer.ConditionObject)condition);
}
public int getWaitQueueLength(Condition condition) {
    if (condition == null)
        throw new NullPointerException();
    if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
        throw new IllegalArgumentException("not owner");
    return sync.getWaitQueueLength((AbstractQueuedSynchronizer.ConditionObject)condition);
}
protected Collection<Thread> getWaitingThreads(Condition condition) {
    if (condition == null)
        throw new NullPointerException();
    if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
        throw new IllegalArgumentException("not owner");
    return sync.getWaitingThreads((AbstractQueuedSynchronizer.ConditionObject)condition);
}
複製代碼

ReentrantLock 與 synchronized 的區別

前面提到 ReentrantLock 提供了比 synchronized 更加靈活和強大的鎖機制,那麼它的靈活和強大之處在哪裏呢?他們之間又有什麼相異之處呢?

首先他們確定具備相同的功能和內存語義。

  1. 與 synchronized 相比,ReentrantLock提供了更多,更加全面的功能,具有更強的擴展性。例如:時間鎖等候,可中斷鎖等候,鎖投票。
  2. ReentrantLock 還提供了條件 Condition ,對線程的等待、喚醒操做更加詳細和靈活,因此在多個條件變量和高度競爭鎖的地方,ReentrantLock 更加適合(之後會闡述Condition)。
  3. ReentrantLock 提供了可輪詢的鎖請求。它會嘗試着去獲取鎖,若是成功則繼續,不然能夠等到下次運行時處理,而 synchronized 則一旦進入鎖請求要麼成功要麼阻塞,因此相比 synchronized 而言,ReentrantLock會不容易產生死鎖些。
  4. ReentrantLock 支持更加靈活的同步代碼塊,可是使用 synchronized 時,只能在同一個 synchronized 塊結構中獲取和釋放。注意,ReentrantLock 的鎖釋放必定要在 finally 中處理,不然可能會產生嚴重的後果。
  5. ReentrantLock 支持中斷處理,且性能較 synchronized 會好些。
相關文章
相關標籤/搜索