j.u.c系列(05)---之重入鎖:ReentrantLock

寫在前面

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

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

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

獲取鎖

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

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

lock方法:線程

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

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

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

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

該方法主要邏輯:首先判斷同步狀態state == 0 ?,若是是表示該鎖尚未被線程持有,直接經過CAS獲取同步狀態,若是成功返回true。若是state != 0,則判斷當前線程是否爲獲取鎖的線程,若是是則獲取鎖,成功返回true。成功獲取鎖的線程再次獲取鎖,這是增長了同步狀態state。隊列

釋放鎖

獲取同步鎖後,使用完畢則須要釋放鎖,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;
    }

與獲取同步狀態的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更加靈活和強大的鎖機制,那麼它的靈活和強大之處在哪裏呢?他們之間又有什麼相異之處呢?

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

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