java鎖(5)可重入鎖ReentrantLock實現詳解

1、ReentrantLock的特性

ReentrantLock是Java併發包中提供的一個可重入的互斥鎖。ReentrantLock和synchronized在基本用法,行爲語義上都是類似的,同樣都具有可重入性。只不過相比原生的Synchronized,ReentrantLock增加了一些高級的擴展功能,比如它可以實現公平鎖,同時也可以綁定多個Conditon。

可重入性:

是指可以支持一個線程對鎖的重複獲取與釋放。原生的synchronized就具有可重入性,一個用synchronized修飾的遞歸方法,當線程在執行期間,它是可以反覆獲取到鎖的,而不會出現自己把自己鎖死的情況。ReentrantLock也是如此,在調用lock()方法時,已經獲取到鎖的線程,能夠再次調用lock()方法獲取鎖而不被阻塞,並且lock和unlock的調用次數必須相等纔會釋放鎖。

公平鎖/非公平鎖:

公平鎖:是指線程獲取鎖的順序和調用lock的順序一樣,FIFO(先進先出)方式獲取和釋放鎖。

非公平鎖:線程獲取鎖的順序和調用lock的順序無關,能否獲取鎖取決於調用時機。

synchronized是非公平鎖,ReentrantLock默認也是非公平的,但是可以通過帶boolean參數的構造方法指定使用公平鎖,但非公平鎖的性能一般要優於公平鎖。

2、ReentrantLock實現

ReentrantLock的所有鎖相關操作都是通過Sync類實現,Sync繼承於AbstractQueuedSynchronizer同步隊列,並實現一些通用的接口實現。

NonfairSync繼承於Sync,實現了非公平的方式獲取鎖;

FairSync繼承於Sync,實現了公平的方式獲取鎖;

ReentrantLock類實現結構如下:

2.1、Sync實現

//sync繼承於AQS
abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = -5179523762034025860L;

    //抽象方法,需子類實現
    abstract void lock();

    //實現了非公平方式獲取鎖
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        //獲取當前鎖狀態,此狀態c>0表示有線程獲取到鎖,重入的次數爲c
        int c = getState();
        //無線程獲取鎖?
        if (c == 0) {
            //CAS方式獲取鎖,成功則設置獲取獨佔鎖的線程
            if (compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        //獲得鎖的線程就是當前線程?則獲取次數加1,並設置狀態(即次數)
        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;
    }

    //釋放鎖
    protected final boolean tryRelease(int releases) {
        //計算需要更新的狀態值
        int c = getState() - releases;
        //若當前線程未獲得鎖,則拋出異常
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;
        //若將要更新的狀態值爲0,表示當前線程最後一次釋放鎖;
        //此時鎖纔會真正的釋放,其他線程才能獲取;
        //否則表示當前線程獲取鎖的次數大於釋放鎖的次數
        if (c == 0) {
            free = true;
            //清除記錄的獨佔鎖的線程
            setExclusiveOwnerThread(null);
        }
        //更新狀態
        setState(c);
        return free;
    }

    //判斷當前線程釋放獲取獨佔鎖
    protected final boolean isHeldExclusively() {
       //當前線程爲記錄的獲取獨佔鎖的線程,則表示當前線程獲得獨佔鎖
        return getExclusiveOwnerThread() == Thread.currentThread();
    }

    //創建新的條件隊列
    final ConditionObject newCondition() {
        return new ConditionObject();
    }

    
    //獲取獲取獨佔鎖的線程對象
    final Thread getOwner() {
        return getState() == 0 ? null : getExclusiveOwnerThread();
    }

    //獲取當前線程獲取鎖的次數
    final int getHoldCount() {
        return isHeldExclusively() ? getState() : 0;
    }
    
    //判斷鎖是否被佔用
    final boolean isLocked() {
        return getState() != 0;
    }
}

2.2、非公平鎖NonfairSync的實現

//非公平鎖NonfairSync的實現,其繼承於Sync
static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

    //lock接口實現;
    //首先通過CAS試圖獲取鎖,獲取成功則設置鎖的Owner;
    //否則調用acquire獲取鎖,acquire又或調用tryAcquire獲取鎖,
    //而tryAcquire是通過非公平的方式獲取鎖。
    final void lock() {
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }

    //非公平方式獲取鎖
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

2.3、公平鎖FairSync的實現

//公平鎖FairSync 的實現,其繼承於Sync
static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;

    //lock接口實現,自己調用acquire獲取鎖;
    //acquire又會調用tryAcquire獲取鎖,而tryAcquire是通過公平(FIFO)
    //的方式獲取鎖。
    final void lock() {
        acquire(1);
    }

    //公平的方式獲取鎖
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        //獲取當前鎖狀態,此狀態c>0表示有線程獲取到鎖,重入的次數爲c
        int c = getState();
        //無線程獲取鎖?
        if (c == 0) {
            //當前節點無前驅節點並且當前線程CAS更新狀態成功;、
            //表示當前線程公平的獲取到鎖
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        //獲得鎖的線程就是當前線程?則獲取次數加1,並設置狀態(即次數)
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
}

2.4、鎖可重入的實現原理

通過以上源碼分析可知,當某個節點獲取到鎖時,會通過setExclusiveOwnerThread()方法記錄獲取獨佔鎖的線程Thread;當某個線程獲取鎖時,當鎖已被佔用,會判斷佔用鎖的線程是否爲當前線程;是則直接更新鎖狀態,表示獲取到鎖;否則獲取鎖失敗。

2.5、鎖公平與非公平的實現原理

公平鎖是通過FairSync實現的,其在tryAcquire獲取鎖時,會判斷同步隊列中當前節點是否有前驅節點;有前驅節點,則獲取鎖失敗,進入同步隊列,等待獲取鎖;無前驅節點時,表示當前節點是同步隊列中等待鎖時間最長的節點,則當前節點優先獲取鎖資源。

非公平鎖是通過NonfairSync實現的,其在lock及tryAcquire時,會先通過CAS的方式嘗試獲取鎖,獲取失敗纔會進入同步隊列等待。這就導致當某個線程剛釋放鎖,而同步隊列中被unpark的頭節點還未CAS獲取到鎖的時間間隙,當前線程先於同步隊列頭結點通過CAS獲取鎖。使得某些線程會等待很長時間纔會獲得鎖,這是非公平性的。

ReentrantLock的構造方法如下:

//無參構造方法,會通過非公平的方式實現鎖
public ReentrantLock() {
    sync = new NonfairSync();
}

//帶參數的構造方法;
//fire=true:會通過公平的方式構造鎖;
//fire=false:會通過非公平的方式構造鎖;
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章