高并發(fā)編程-ReentrantLock非公平鎖深入解析

要點(diǎn)解說

ReentrantLock是一個(gè)可重入的互斥鎖,它不但具有synchronized實(shí)現(xiàn)的同步方法和同步代碼塊的基本行為和語義簿透,而且具備很強(qiáng)的擴(kuò)展性。ReentrantLock提供了公平鎖和非公平鎖兩種實(shí)現(xiàn)解藻,在默認(rèn)情況下構(gòu)造的ReentrantLock實(shí)例是非公平鎖老充,可以在創(chuàng)建ReentrantLock實(shí)例的時(shí)候通過指定公平策略參數(shù)來指定是使用公平鎖還是非公平鎖。本篇將基于JDK7深入源碼解析非公平鎖的實(shí)現(xiàn)原理螟左。

實(shí)例演示

下面是使用ReentrantLock非公平鎖的典型代碼啡浊。

class X {
  private final ReentrantLock lock = new ReentrantLock();

  public void m() {
    lock.lock();
    try {
      // do some thing
    } finally {
      lock.unlock()
    }
  }
}

方法解析

  1. ReentrantLock()創(chuàng)建一個(gè)非公平鎖ReentrantLock實(shí)例;
  2. ReentrantLock(boolean fair)根據(jù)公平策略fair參數(shù)創(chuàng)建ReentrantLock實(shí)例胶背;
  3. lock()獲取鎖巷嚣;
  4. unlock()釋放鎖;
  5. newCondition()返回與此ReentrantLock實(shí)例一起使用的Condition的實(shí)例钳吟;
  6. getHoldCount()獲取當(dāng)前線程持有此鎖的次數(shù)廷粒;
  7. getQueueLength()返回正在等待獲取此鎖的線程數(shù);
  8. getWaitQueueLength(Condition condition)返回等待與此鎖相關(guān)的給定條件的線程數(shù);
  9. hasQueuedThread(Thread thread)返回指定線程是否正在等待獲取此鎖坝茎;
  10. hasQueuedThreads()返回是否有線程正在等待獲取此鎖涤姊;
  11. hasWaiters(Condition condition)返回是否有線程正在等待與此鎖有關(guān)的給定條件;
  12. isFair()返回鎖是否是公平鎖嗤放;
  13. isHeldByCurrentThread()返回當(dāng)前線程是否持有此鎖思喊;
  14. tryLock()嘗試獲取鎖,僅在調(diào)用時(shí)鎖未被其它線程持有時(shí)才可以獲取該鎖次酌;
  15. tryLock(long timeout, TimeUnit unit)嘗試獲取鎖恨课,如果鎖在指定等待時(shí)間內(nèi)沒有被另一個(gè)線程持有,并且當(dāng)前線程未被中斷和措,則可以獲取該鎖庄呈。

源碼解析

從上面的方法解析可以看到它有很多方法,本文將重點(diǎn)深入分析lock()和unlock()方法派阱。首先诬留,從構(gòu)造函數(shù)開始。

    //默認(rèn)構(gòu)造方法創(chuàng)建的是非公平鎖
    public ReentrantLock() {
        //構(gòu)造非公平鎖
        sync = new NonfairSync();
    }

    public ReentrantLock(boolean fair) {
        //根據(jù)公平策略構(gòu)造公平鎖或非公平鎖
        sync = fair ? new FairSync() : new NonfairSync();
    }

從上面的代碼可以看到贫母,默認(rèn)構(gòu)造函數(shù)使用NonfairSync創(chuàng)建的是非公平鎖文兑,當(dāng)然也可以指定公平策略參數(shù)fair為false創(chuàng)建非公平鎖。NonfairSync是ReentrantLock中的靜態(tài)內(nèi)部類腺劣,它繼承了Sync绿贞,而Sync是ReentrantLock中的抽象靜態(tài)內(nèi)部類,Sync又繼承自AbstractQueuedSynchronizer橘原,分析到這里可以看到籍铁,ReentrantLock的具體實(shí)現(xiàn)使用了AQS。

    abstract static class Sync extends AbstractQueuedSynchronizer {
        //此處省略內(nèi)部代碼趾断,后面具體分析
    }

    static final class NonfairSync extends Sync{
        //此處省略內(nèi)部代碼拒名,后面具體分析
    }

當(dāng)調(diào)用lock()方法獲取鎖時(shí),具體實(shí)現(xiàn)代碼如下芋酌。

    //ReentrantLock類的lock方法
    public void lock() {
        //此時(shí)的sync是NonfairSync的實(shí)例對(duì)象增显,所以執(zhí)行sync.lock()將執(zhí)行NonfairSync類的lock()方法
        sync.lock();
    }

    //NonfairSync類的lock()方法
    final void lock() {
        //通過CAS設(shè)置AQS中state的值
        //如果此時(shí)state值等于0,也就是鎖沒有被其它線程持有脐帝,則返回true
        if (compareAndSetState(0, 1))
            //設(shè)置獨(dú)占鎖的當(dāng)前所有者為當(dāng)前線程
            setExclusiveOwnerThread(Thread.currentThread());
        else
            //否則同云,嘗試獲取獨(dú)占鎖
            //acquire方法繼承自AbstractQueuedSynchronizer
            acquire(1);
    }

    //AbstractQueuedSynchronizer類的acquire方法
    public final void acquire(int arg) {
        //調(diào)用tryAcquire方法嘗試獲取鎖,如果成功則返回堵腹,否則先執(zhí)行addWaiter方法炸站,再執(zhí)行acquireQueued方法
        //因?yàn)楣芥i和非公平鎖對(duì)鎖持有的實(shí)現(xiàn)不同,所以這里的tryAcquire使用的是NonfairSync類中的實(shí)現(xiàn)
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            //中斷當(dāng)前線程
            selfInterrupt();
    }

    //NonfairSync類的tryAcquire方法
    protected final boolean tryAcquire(int acquires) {
        //nonfairTryAcquire方法繼承自Sync類
        return nonfairTryAcquire(acquires);
    }

    //Sync類的nonfairTryAcquire方法
    final boolean nonfairTryAcquire(int acquires) {
        //獲取當(dāng)前線程
        final Thread current = Thread.currentThread();
        //獲取AQS中state當(dāng)前值
        int c = getState();
        //如果state當(dāng)前值等于0
        if (c == 0) {
            //使用CAS修改state值
            //如果此時(shí)state值等于0疚顷,也就是鎖沒有被其它線程持有旱易,則修改成功
            if (compareAndSetState(0, acquires)) {
                //設(shè)置獨(dú)占鎖的當(dāng)前所有者為當(dāng)前線程
                setExclusiveOwnerThread(current);
                //獲取到鎖
                return true;
            }
        }
        //如果獨(dú)占鎖的當(dāng)前所有者是當(dāng)前線程,鎖重入的情況
        else if (current == getExclusiveOwnerThread()) {
            //將state值加一
            int nextc = c + acquires;
            //判斷新值是否溢出
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            //更新state值
            setState(nextc);
            //獲取到鎖
            return true;
        }
        //獲取不到鎖
        return false;
    }

    //上面分析的代碼可以清晰的看到,當(dāng)鎖沒有被線程持有時(shí)的獲取過程咒唆,
    //但是,如果鎖此時(shí)被其它線程持有释液,即執(zhí)行tryAcquire方法返回false全释,
    //此時(shí)將需要先執(zhí)行addWaiter方法,將當(dāng)前線程封裝成Node節(jié)點(diǎn)误债,并將這個(gè)Node節(jié)點(diǎn)插入到同步等待隊(duì)列的尾部浸船,
    //然后執(zhí)行acquireQueued方法,阻塞當(dāng)前線程寝蹈,具體實(shí)現(xiàn)代碼分析如下:

    //addWaiter方法繼承自AQS
    //將當(dāng)前線程封裝成Node節(jié)點(diǎn)李命,并將這個(gè)Node節(jié)點(diǎn)插入到同步等待隊(duì)列的尾部
    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }

    //acquireQueued方法繼承自AQS
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                //獲取當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)
                final Node p = node.predecessor();
                //如果當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)是頭結(jié)點(diǎn),并且可以獲取到鎖箫老,跳出循環(huán)并返回false
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)不是頭結(jié)點(diǎn)封字,或不可以獲取到鎖
                //shouldParkAfterFailedAcquire方法檢查當(dāng)前節(jié)點(diǎn)在獲取鎖失敗后是否要被阻塞
                //如果shouldParkAfterFailedAcquire方法執(zhí)行結(jié)果是當(dāng)前節(jié)點(diǎn)線程需要被阻塞,則執(zhí)行parkAndCheckInterrupt方法阻塞當(dāng)前線程
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

    //parkAndCheckInterrupt方法繼承自AQS耍鬓,用于阻塞當(dāng)前線程
    private final boolean parkAndCheckInterrupt() {
        //阻塞當(dāng)前線程阔籽,當(dāng)前線程執(zhí)行到這里即被掛起,等待被喚醒
        //當(dāng)當(dāng)前節(jié)點(diǎn)的線程被喚醒的時(shí)候牲蜀,會(huì)繼續(xù)嘗試獲取鎖
        LockSupport.park(this);
        return Thread.interrupted();
    }

當(dāng)調(diào)用unlock()方法釋放持有的鎖時(shí)笆制,具體實(shí)現(xiàn)代碼如下。

    //ReentrantLock類的unlock方法
    public void unlock() {
        //此時(shí)的sync是NonfairSync的實(shí)例對(duì)象
        //在NonfairSync中沒有重寫release方法涣达,release方法繼承自AQS在辆,所以執(zhí)行AQS的release方法
        sync.release(1);
    }

    //AQS的release方法
    public final boolean release(int arg) {
        //嘗試釋放持有的鎖
        //如果釋放成功,則從同步等待隊(duì)列的頭結(jié)點(diǎn)開始喚醒等待線程
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                //喚醒同步等待隊(duì)列中的阻塞線程
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

    //嘗試釋放持有的鎖
    protected final boolean tryRelease(int releases) {
        //將state值減去1
        int c = getState() - releases;
        //如果當(dāng)前線程不是持有鎖的線程度苔,拋異常
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;
        //如果state的值等于零匆篓,則表明此鎖已經(jīng)完全被釋放
        //如果state的值不等于零,則表明線程持有的鎖(可重入鎖)還沒有完全被釋放
        if (c == 0) {
            //free=true表示鎖以被完全釋放
            free = true;
            setExclusiveOwnerThread(null);
        }
        setState(c);
        return free;
    }

    //喚醒阻塞線程
    private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        Node s = node.next;
        //如果后繼節(jié)點(diǎn)為空或已被取消林螃,則從尾部開始找到等待隊(duì)列中第一個(gè)waitStatus<=0奕删,即未被取消的節(jié)點(diǎn)
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            //喚醒等待隊(duì)列節(jié)點(diǎn)中的線程
            //之前執(zhí)行到parkAndCheckInterrupt方法的線程繼續(xù)執(zhí)行,再次嘗試獲取鎖
            LockSupport.unpark(s.thread);
    }

原理總結(jié)

A疗认、B兩個(gè)線程同時(shí)執(zhí)行l(wèi)ock()方法獲取鎖完残,假設(shè)A先執(zhí)行獲取到鎖,此時(shí)state值加1横漏,如果線程A在繼續(xù)執(zhí)行的過程中又執(zhí)行了lock()方法谨设,線程A會(huì)直接獲取鎖,同時(shí)state值加1缎浇,state的值可以簡(jiǎn)單理解為線程A執(zhí)行l(wèi)ock()方法的次數(shù)扎拣;當(dāng)線程B執(zhí)行l(wèi)ock()方法獲取鎖時(shí),會(huì)將線程B封裝成Node節(jié)點(diǎn),并將其插入到同步等待隊(duì)列的尾部二蓝,然后阻塞當(dāng)前線程誉券,等待被喚醒再次嘗試獲取鎖;線程A每次執(zhí)行unlock()方法都會(huì)將state值減1刊愚,直到state的值等于零則表示完全釋放掉了線程A持有的鎖踊跟,此時(shí)將從同步等待隊(duì)列的頭節(jié)點(diǎn)開始喚醒阻塞的線程,阻塞線程恢復(fù)執(zhí)行鸥诽,再次嘗試獲取鎖商玫。ReentrantLock非公平鎖的實(shí)現(xiàn)使用了AQS的同步等待隊(duì)列和state。

END

如果覺得有收獲牡借,記得關(guān)注拳昌、點(diǎn)贊、轉(zhuǎn)發(fā)钠龙。

image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末炬藤,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子碴里,更是在濱河造成了極大的恐慌刻像,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,383評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件并闲,死亡現(xiàn)場(chǎng)離奇詭異细睡,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)帝火,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門溜徙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人犀填,你說我怎么就攤上這事蠢壹。” “怎么了九巡?”我有些...
    開封第一講書人閱讀 157,852評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵图贸,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我冕广,道長(zhǎng)疏日,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,621評(píng)論 1 284
  • 正文 為了忘掉前任撒汉,我火速辦了婚禮沟优,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘睬辐。我一直安慰自己挠阁,他們只是感情好宾肺,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著侵俗,像睡著了一般锨用。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上隘谣,一...
    開封第一講書人閱讀 49,929評(píng)論 1 290
  • 那天黔酥,我揣著相機(jī)與錄音,去河邊找鬼洪橘。 笑死,一個(gè)胖子當(dāng)著我的面吹牛棵帽,可吹牛的內(nèi)容都是我干的熄求。 我是一名探鬼主播,決...
    沈念sama閱讀 39,076評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼逗概,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼弟晚!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起逾苫,我...
    開封第一講書人閱讀 37,803評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤卿城,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后铅搓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體瑟押,經(jīng)...
    沈念sama閱讀 44,265評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評(píng)論 2 327
  • 正文 我和宋清朗相戀三年星掰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了多望。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,716評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡氢烘,死狀恐怖怀偷,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情播玖,我是刑警寧澤椎工,帶...
    沈念sama閱讀 34,395評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站蜀踏,受9級(jí)特大地震影響维蒙,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜果覆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評(píng)論 3 316
  • 文/蒙蒙 一木西、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧随静,春花似錦八千、人聲如沸吗讶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽照皆。三九已至,卻和暖如春沸停,著一層夾襖步出監(jiān)牢的瞬間膜毁,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工愤钾, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留瘟滨,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,488評(píng)論 2 361
  • 正文 我出身青樓能颁,卻偏偏與公主長(zhǎng)得像杂瘸,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子伙菊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容

  • “今天你錯(cuò)過了誰财剖,誰又失去了你呢六剥?” 今天被分手不久的小R的前男友小q發(fā)了這樣一條動(dòng)態(tài),小R看到了峰伙,她也看了這部電...
    你是我未完成的歌閱讀 150評(píng)論 0 0
  • 工具: 毛筆:秋紅齋秀意疗疟、小小染、老人頭勾線 顏料:溫莎牛頓學(xué)生用 畫紙:寶虹棉漿32k 第一步瞳氓,硫酸紙線稿策彤,這個(gè)...
    花梨花Lena閱讀 597評(píng)論 1 6
  • 1、 今天我在農(nóng)場(chǎng)里發(fā)生了一件奇怪的事情匣摘,那就是在讀經(jīng)典前店诗,我媽媽在做后廚,我就做得非常的好音榜,可是呢庞瘸,老媽一來,我...
    紫彤閱讀 158評(píng)論 0 0
  • 不用看年月日 天狗一直在吞噬月亮 多少代人都在探尋 為什么我們會(huì)老去 這樣的道理 你沉靜下就可以 清醒的看見 文字...
    原郎閱讀 293評(píng)論 0 8