詳解java併發之重入鎖-ReentrantLock

前言
目前主流的鎖有兩種,一種是synchronized,另外一種就是ReentrantLock,JDK優化到如今目前爲止synchronized的性能已經和重入鎖不分伯仲了,可是重入鎖的功能和靈活性要比這個關鍵字多的多,因此重入鎖是能夠徹底替代synchronized關鍵字的。下面就來介紹這個重入鎖。java

正文
ReentrantLock重入鎖是Lock接口裏最重要的實現,也是在實際開發中應用最多的一個,我這篇文章更接近實際開發的應用場景,爲開發者提供直接上手應用。因此不是全部方法我都講解,有些冷門的方法我不會介紹或一句帶過。
1、首先先看聲明一個重入鎖須要使用到那幾個構造方法面試

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

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

推薦聲明方式併發

private static ReentrantLock lock = new ReentrantLock(true);
private static ReentrantLock locka = new ReentrantLock();

重點說明:性能

ReentrantLock提供了兩個構造方法,對應兩種聲明方式。優化

第一種聲明的是公平鎖,所謂公平鎖,就是按照時間前後順序,使先等待的線程先獲得鎖,並且,公平鎖不會產生飢餓鎖,也就是隻要排隊等待,最終能等待到獲取鎖的機會。ui

第二種聲明的是非公平鎖,所謂非公平鎖就和公平鎖概念相反,線程等待的順序並不必定是執行的順序,也就是後來進來的線程可能先被執行。線程

ReentrantLock默認是非公平鎖,由於:公平鎖實現了先進先出的公平性,可是因爲來一個線程就加入隊列中,每每都須要阻塞,再由阻塞變爲運行,這種上下文切換是很是好性能的。非公平鎖因爲容許插隊因此,上下文切換少的多,性能比較好,保證的大的吞吐量,可是容易出現飢餓問題。因此實際生產也是較多的使用非公平鎖。code

非公平鎖調用的是NonfairSync方法。orm

2、加入鎖以後lock方法究竟是怎麼處理的(只講非公平鎖)
剛纔咱們說若是是非公平鎖就調用NonfairSync方法,那咱們就來看看這個方法都作來什麼。接口

static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

重點說明:

讀前先知:ReentrantLock用state表示「持有鎖的線程已經重複獲取該鎖的次數」。當state(下文用狀態二子代替)等於0時,表示當前沒有線程持有鎖)。
第一步調用compareAndSetState方法,傳了第一參數是指望值0,第二個參數是實際值1,當前這個方法實際是調用了unsafe.compareAndSwapInt實現CAS操做的,也就是上鎖以前狀態必須是0,若是是0調用setExclusiveOwnerThread方法

private transient Thread exclusiveOwnerThread;

    protected final void setExclusiveOwnerThread(Thread thread) {
        exclusiveOwnerThread = thread;
    }

能夠看出setExclusiveOwnerThread就是線程設置爲當前線程,此時說明有一名線程已經拿到了鎖。你們都是CAS有三個值,若是舊值等於預期值,就把新值賦予上,因此當前線程獲得了鎖就會把狀態置爲1。

第二步是compareAndSetState方法返回false時,此時調用的是acquire方法,參數傳1

tryAcquire()方法實際是調用了nonfairTryAcquire()方法。

public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

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

註釋上說的很明白,請求獨佔鎖,忽略全部中斷,至少執行一次tryAcquire,若是成功就返回,不然線程進入阻塞--喚醒兩種狀態切換中,直到tryAcquire成功。詳情見連接tryAcquire()、addWaiter()、acquireQueued()挨個分析。
好,到日前爲止你們清楚了lock()方法到調用過程,清楚了,爲何只有獲得鎖的當前線程才能夠執行,沒有獲得的會在隊列裏不停的利用CAS原理試圖獲得鎖,CAS很高效,也就是,爲何ReentrantLock比synchronized高效的緣由,缺點是很浪費cpu資源。

3、全部線程都執行完畢後調用unlock()方法

unlock()方法是經過AQS的release(int)方法實現的,咱們能夠看一下:

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

public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

tryRelease()是由子類實現的,咱們來看一下ReentrantLock中的Sync對它的實現:

protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

先經過getState得到狀態標識,若是這個標識和要釋放的數量相等,就會把當前佔有鎖的線程設置爲null,實現鎖的釋放,而後返回true,不然把狀態標識減去releases再返回false。
以上所述是小編給你們介紹的java併發之重入鎖-ReentrantLock詳解整合,但願對你們有所幫助,若是你們有任何疑問請給我留言,小編會及時回覆你們的。

但願求職者在面試的時候,碰到這個問題須要聰明一點,回答得巧妙一些,不要太耿直,也不要弄虛做假。  

相關文章
相關標籤/搜索