Java併發程式設計——synchronize 和 ReentrantLock

it_was發表於2020-09-28

顯式鎖ReentrantLock內建鎖synchronize不同,它並不是一種替代內建鎖的方式,而是作為一種可選擇的高階功能。

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}

ReentrantLock 實現了 Lock介面,意味著其具有無條件的,可中斷的,可輪詢以及定時的鎖獲取操作,所有的加鎖與解鎖必須顯式的呼叫!

2.1 公平鎖與非公平鎖

我們先來討論以下ReentrantLock高階功能之一:公平鎖與非公平鎖!:boom:
ReentrantLock 預設採用非公平鎖

    public ReentrantLock() {
        sync = new NonfairSync(); //此處預設是非公平鎖!
    }
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

再看看公平鎖和非公平鎖的實現,兩者都是實現了Sync的靜態內部類。先看下公平鎖的核心方法:

    /**
     * Sync object for fair locks
     */
     //公平鎖!!!!!!!
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            acquire(1);
        }
        //注意這個acquire方法是在AbstractQueuedSynchronizer中實現的!並不是公平鎖的方法!
        public final void acquire(int arg) {
            if (!tryAcquire(arg) && //當前執行緒是否能獲得鎖!
                acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//獲取不了加入到等待佇列的尾部
                selfInterrupt(); //最終阻塞當前執行緒!
        }

        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState(); //獲取鎖計數資訊
            if (c == 0) { //判斷是否為0,即當前是否有執行緒正持有這個鎖!
                if (!hasQueuedPredecessors() &&  //首先會判斷是否有執行緒正在等待,如果有等待,為保證公平性跳到最底部返回false,即試圖獲取鎖失敗!
                    compareAndSetState(0, acquires)) { //然後通過cas操作競爭鎖
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) { //ok,有執行緒持有這個鎖,就判斷一下是否是本執行緒持有這個鎖,ok,持有,重入!
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc); //更改鎖計數資訊
                return true;
            }
            return false; // 都不是,直接返回false
        }
    }

再看下非公平鎖的

static final class NonfairSync extends Sync {
    final void lock() {
        //  和公平鎖相比,這裡會直接先進行一次CAS,成功就返回了
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }
    // AbstractQueuedSynchronizer.acquire(int arg)
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}
/**
 * Performs non-fair tryLock.  tryAcquire is implemented in
 * subclasses, but both need nonfair try for trylock method.
 */
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;
}

總結:raising_hand:

  • 非公平鎖在呼叫 lock 後,首先就會呼叫 CAS 進行一次搶鎖,如果這個時候恰巧鎖沒有被佔用,那麼直接就獲取到鎖返回了。
  • 非公平鎖在 CAS 失敗後,和公平鎖一樣都會進入到 tryAcquire 方法,在 tryAcquire 方法中,如果發現鎖這個時候被釋放了(state == 0),非公平鎖會直接 CAS 搶鎖,但是公平鎖會判斷等待佇列是否有執行緒處於等待狀態,如果有則不去搶鎖,乖乖排到後面。
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章