java併發編程充入鎖:ReentrantLock

簡述

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

API介紹以下:一個可重入的互斥鎖定Lock,它具備與使用synchronized方法和語句所訪問的隱士監視器鎖定相同的一些基本行爲和語義,但功能更強大。ReentrantLock將由最近成功得到鎖定,而且沒有釋放該鎖定的線程擁有。當鎖定沒有被另外一個線程所擁有時,調用Lock的線程將成功獲取該鎖定並返回。若是當前線程已經擁有該鎖定,此方法將當即返回。可使用isHeldByCurrentThread() 和 getHoldCount() 方法來檢查此狀況是否發生。多線程

ReentrantLock還提供了公平鎖也非公平鎖的選擇,構造方法接受一個可選的公平參數(默認非公平鎖),當設置爲true時,表示公平鎖,不然爲非公平鎖。公平鎖與非公平鎖的區別在於公平鎖的鎖獲取是有順序的。可是公平鎖的效率每每沒有非公平鎖的效率高,在許多線程訪問的狀況下,公平鎖表現出較低的吞吐量。ui

輸入圖片說明

獲取鎖

咱們通常都是這麼使用ReentrantLock獲取鎖的:線程

//非公平鎖
ReentrantLock lock = new ReentrantLock();
lock.lock();

lock方法:code

public void lock() {
        sync.lock();
    }

Sync爲ReentrantLock裏面的一個內部類,它繼承AQS(AbstractQueuedSynchronizer),它有兩個子類:公平鎖FairSync和非公平鎖NonfairSync。繼承

ReentrantLock裏面大部分的功能都是委託給Sync來實現的,同時Sync內部定義了lock()抽象方法由其子類去實現,默認實現了nonfairTryAcquire(int acquires)方法,能夠看出它是非公平鎖的默認實現方式。下面咱們看非公平鎖的lock()方法:遞歸

final void lock() {
        //嘗試獲取鎖
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            //獲取失敗,調用AQS的acquire(int arg)方法
            acquire(1);
    }

首先會第一次嘗試快速獲取鎖,若是獲取失敗,則調用acquire(int arg)方法,該方法定義在AQS中,以下:隊列

public final void acquire(int arg) {
         //tryAcquire獲取鎖不成功時,加入到隊列中
         //不然,發生異常,自我中斷
        if (!tryAcquire(arg) &&
                acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

這個方法首先調用tryAcquire(int arg)方法,在AQS中講述過,tryAcquire(int arg)須要自定義同步組件提供實現,非公平鎖實現以下:圖片

protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(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;
    }

釋放鎖

獲取同步鎖後,使用完畢則須要釋放鎖,ReentrantLock提供了unlock釋放鎖:內存

public void unlock() {
        sync.release(1);
    }

unlock內部使用Sync的release(int arg)釋放鎖,release(int arg)是在AQS中定義的:

public final boolean release(int arg) {
        //若是鎖釋放成功,則調取下一個等待節點
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

AQS的unparkSuccessor 方法,獲取等待隊列中的節點。

private void unparkSuccessor(Node node) {
    //這裏,node通常爲當前線程所在的結點。
    int ws = node.waitStatus;
    if (ws < 0)//置零當前線程所在的結點狀態,容許失敗。
        compareAndSetWaitStatus(node, ws, 0);

    Node s = node.next;//找到下一個須要喚醒的結點s
    if (s == null || s.waitStatus > 0) {//若是爲空或已取消
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)//從這裏能夠看出,<=0的結點,都是還有效的結點。
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);//喚醒
}

與獲取同步狀態的acquire(int arg)方法類似,釋放同步狀態的tryRelease(int arg)一樣是須要自定義同步組件本身實現:

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;
    }

只有當同步狀態完全釋放後該方法纔會返回true。當state == 0 時,則將鎖持有線程設置爲null,free= true,表示釋放成功。

公平鎖與非公平鎖

公平鎖與非公平鎖的區別在於獲取鎖的時候是否按照FIFO的順序來。釋放鎖不存在公平性和非公平性,上面以非公平鎖爲例,下面咱們來看看公平鎖的tryAcquire(int arg):

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(),定義以下:

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。(判斷是不是等待時間最長的哪個。)

ReentrantLock與synchronized的區別

前面提到ReentrantLock提供了比synchronized更加靈活和強大的鎖機制,那麼它的靈活和強大之處在哪裏呢?他們之間又有什麼相異之處呢? 首先他們確定具備相同的功能和內存語義。

相關文章
相關標籤/搜索