ReentrantLock 源碼閱讀

一 API 閱讀

一種可重入的互斥鎖为障。擁有和 synchronized 關(guān)鍵字相同的功能,除此之外嚣鄙,也有一定的功能擴(kuò)展吻贿。

一個(gè) ReentrantLock 鎖會(huì)被成功調(diào)用了 lock 方法,且還沒有 unlock 的線程持有拗慨。檢查一個(gè)線程是否持有鎖的方法是 isHeldByCurrentThreadgetHoldCount廓八。

構(gòu)造函數(shù)可以包含一個(gè)可選的 boolean 值,表示構(gòu)建的鎖是一個(gè) 公平鎖 還是
非公平鎖赵抢。使用默認(rèn)的無參構(gòu)造時(shí)剧蹂,這個(gè)參數(shù)默認(rèn)為 false 即非公平鎖。當(dāng)入?yún)?true 的時(shí)候烦却,表示這是一個(gè)公平鎖宠叼,排隊(duì)的隊(duì)列里等待最久的線程最先獲得鎖。傳入?yún)?shù)為 false 的時(shí)候表示這是一個(gè)非公平鎖其爵,不會(huì)遵循公平鎖里線程獲取鎖的策略冒冬。在競爭線程較多的情況下,使用公平鎖會(huì)導(dǎo)致較低的吞入量摩渺。

需要注意的是简烤,不定期地調(diào)用 tryLock 方法,會(huì)讓爭用線程不遵循公平鎖的競爭模式摇幻。當(dāng)恰巧鎖資源被釋放横侦,而還有排隊(duì)線程的時(shí)候挥萌,主動(dòng)調(diào)用方法可能會(huì)成功提前獲取到鎖。

使用 ReentrantLock 的常見慣例如下

class X {
    private final ReentrantLock lock = new ReentrantLock();
    
    public void m() {
        lock.lock();
        try {
            // ... method body
        } finally {
            lock.unlock();
        }
    }
}

作為一個(gè)可重入鎖枉侧,ReentrantLock 允許同一個(gè)線程的重入次數(shù)為 Integer.MAX_VALUE引瀑。

二 部分代碼閱讀

看這部分的代碼的時(shí)候,需要結(jié)合前面的文章 AQS 部分一起來看榨馁。

2.1 非公平鎖的 lock 流程

    private final Sync sync;

需要注意憨栽,這里這個(gè)成員變量 sync 是 reentrantLock 實(shí)現(xiàn)同步機(jī)制的核心類。因?yàn)?reentrantLock 使用的是 AQS 同步框架翼虫,而 sync 就是這個(gè) AQS 的內(nèi)部實(shí)現(xiàn)類屑柔。

這里 sync 的實(shí)際實(shí)現(xiàn),在 reentrantLock 里面分成了兩大類蛙讥。一個(gè)是公平鎖實(shí)現(xiàn)锯蛀,另一個(gè)是非公平鎖實(shí)現(xiàn)。這里的編碼 遵循了單一職責(zé)原則次慢,也符合 AQS 同步器框架的推薦做法。

當(dāng)我們使用默認(rèn)的無參構(gòu)造函數(shù)創(chuàng)建一個(gè) reentrantLock 實(shí)例翔曲。然后調(diào)用 lock() 方法迫像,其流程如下:

非公平鎖.png

實(shí)際調(diào)用的方法就是這里的

java.util.concurrent.locks.ReentrantLock.NonfairSync#lock

    final void lock() {
        // cas 方式更新 AQS 的 state 成員值 +1
        if (compareAndSetState(0, 1))
            // 更新成功,設(shè)置獨(dú)占鎖線程引用為當(dāng)前線程
            setExclusiveOwnerThread(Thread.currentThread());
        else
            // cas 更新失敗瞳遍,調(diào)用 AQS 的 acquire 方法
            acquire(1);
    }

先嘗試直接修改 AQS 內(nèi)部維護(hù)的 state 成員變量闻妓,0 表示沒有線程持有鎖,由 CAS 方式更新為 1掠械。如果更新成功由缆,即表示當(dāng)前線程成功持有了這個(gè)可重入獨(dú)占鎖,這時(shí)更新一下獨(dú)占鎖的線程引用為當(dāng)前線程猾蒂。

如果 cas 方式更新 state 字段失敗均唉,那么就調(diào)用 AQS 內(nèi)定義的 acquire 方法來嘗試獲取鎖。這個(gè)方法之前在 AQS 源碼閱讀的時(shí)候詳細(xì)讀過肚菠。通過定義一套模板方法舔箭,來實(shí)現(xiàn)加鎖操作。其中的方法

  • acquireQueued
  • addWaiter

都是 AQS 自己實(shí)現(xiàn)蚊逢,子類需要補(bǔ)充的方法是

  • tryAcquire

在內(nèi)部類

java.util.concurrent.locks.ReentrantLock.NonfairSync

中层扶,這個(gè)方法的實(shí)現(xiàn)指向了

java.util.concurrent.locks.ReentrantLock.NonfairSync#tryAcquire

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

nonfairTryAcquire 代碼如下:

    final boolean nonfairTryAcquire(int acquires) {
        // 獲取當(dāng)前線程
        final Thread current = Thread.currentThread();
        // 獲取 AQS 內(nèi)成員變量 state
        int c = getState();
        // 如果 state 為 0,表示鎖空閑烙荷,嘗試獲取鎖
        if (c == 0) {
            // cas 方式更新 state 字段
            if (compareAndSetState(0, acquires)) {
                // 更新成功镜会,設(shè)置當(dāng)前線程引用為
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        // state 不為 0,表示鎖已經(jīng)被某線程持有终抽,先檢查是不是自己持有
        else if (current == getExclusiveOwnerThread()) {
            // ReentrantLock 支持重入戳表,所以累加 acquire 值
            int nextc = c + acquires;
            // 檢查重入次數(shù)有沒有溢出焰薄,溢出則拋出異常
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            // 未溢出,更新 state 值
            setState(nextc);
            return true;
        }
        // 獲取鎖失敗扒袖,返回false
        return false;
    }
todo nonfairTryAcquire 流程圖

如果此處的 nonfairTryAcquire 方法加鎖失敗塞茅,那么嘗試加鎖的線程會(huì)被加入同步隊(duì)列排隊(duì)(即 AQS 的 addWaiter 和 acquireQueued 方法)。而這個(gè)同步隊(duì)列的排隊(duì)喚醒線程機(jī)制又是默認(rèn)的 非公平鎖 機(jī)制季率。

至此野瘦,我們應(yīng)該知道的是,reentrantLock 的非公平鎖核心機(jī)制是依賴于 AQS 的內(nèi)容實(shí)現(xiàn)的飒泻。reentrantLock 本身也沒有維護(hù)線程等待隊(duì)列鞭光,這是 AQS 的工作。reentrantLock 只是通過內(nèi)部類來實(shí)現(xiàn)了這個(gè)功能泞遗。

2.2 公平鎖的 lock 流程

當(dāng)以如下的方式聲明一個(gè) reentrantLock 對象時(shí)惰许,我們就可以得到一個(gè)公平鎖。

ReentrantLock lock = new ReentrantLock(Boolean.TRUE);

公平鎖和非公平鎖的區(qū)別在于:排隊(duì)線程的獲取鎖時(shí)機(jī)是有順序的史辙,等待最久的線程最先獲得鎖汹买。

與默認(rèn)的 NoFairSync 實(shí)現(xiàn)相比,其他的都一樣聊倔,主要的區(qū)別在自己實(shí)現(xiàn)的 tryAcquire 方法晦毙。

java.util.concurrent.locks.ReentrantLock.FairSync#tryAcquire

    // 公平鎖版本的 tryAcquire
    protected final boolean tryAcquire(int acquires) {
        // 獲取當(dāng)前線程
        final Thread current = Thread.currentThread();
        // 獲取 AQS 同步器維護(hù)的鎖狀態(tài)字段 state
        int c = getState();
        // c == 0 表示當(dāng)前鎖處于空閑狀態(tài),可以嘗試獲取鎖
        if (c == 0) {
            // hasQueuedPredecessors 方法用于判斷當(dāng)前嘗試獲取鎖的線程是否需要排隊(duì)耙蔑,如果不需要排隊(duì)則直接更新 state 字段并設(shè)置獨(dú)占線程的引用见妒,在判斷體內(nèi)返回 true
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        // 走到這里 c != 0,即鎖已被占有甸陌,檢查持有鎖的線程是不是當(dāng)前線程自己
        else if (current == getExclusiveOwnerThread()) {
            // 是當(dāng)前線程持有鎖须揣,增加重入加鎖次數(shù),傳入的 acquires 為 1
            int nextc = c + acquires;
            // 重入次數(shù)超過 Integer.MAX_VALUE 溢出為負(fù)數(shù)钱豁,拋出異常
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            // 未溢出耻卡,設(shè)置更新 state 字段值
            setState(nextc);
            // 返回 true
            return true;
        }
        // 嘗試加鎖失敗,返回 false
        return false;
    }
}

公平鎖與非公平鎖的 tryAcquire 方法寥院,主要區(qū)別在一個(gè)地方

hasQueuedPredecessors

當(dāng)鎖處于空閑狀態(tài)時(shí)劲赠,公平鎖加鎖的前置判斷條件多了這么一個(gè)方法。

在 state = 0 的條件下秸谢,非公平鎖內(nèi)的線程不用檢查 AQS 維護(hù)點(diǎn)隊(duì)列信息而直接嘗試爭用鎖凛澎;

    public final boolean hasQueuedPredecessors() {
        // The correctness of this depends on head being initialized
        // before tail and on head.next being accurate if the current
        // thread is first in queue.
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

拿到 AQS 維護(hù)的線程等待隊(duì)列的頭節(jié)點(diǎn)/尾節(jié)點(diǎn)引用。然后有一個(gè)嵌套的判斷邏輯估蹄,返回 false 表示可以直接加鎖塑煎,返回 true 的時(shí)候就需要入隊(duì)。

第一個(gè)條件 A臭蚁,頭節(jié)點(diǎn)不等于尾節(jié)點(diǎn)最铁,即隊(duì)列中還有在排隊(duì)的線程讯赏。如果這個(gè)條件不滿足(即頭節(jié)點(diǎn)等于尾節(jié)點(diǎn)),說明隊(duì)列中無排隊(duì)線程冷尉,可以直接入隊(duì)漱挎,不需要將現(xiàn)有線程入隊(duì)。此時(shí)觸發(fā)短路邏輯雀哨,直接返回 false磕谅。

第二個(gè)條件組 B,兩個(gè)條件滿足一個(gè)即可

  1. 頭節(jié)點(diǎn)的后繼節(jié)點(diǎn)不為空
  2. 頭節(jié)點(diǎn)的后繼節(jié)點(diǎn)不是當(dāng)前嘗試獲取鎖的節(jié)點(diǎn)雾棺,如果這條為 false膊夹,表示排隊(duì)里下一個(gè)即將拿到鎖的線程就是當(dāng)前線程

在條件 A 返回 true 的情況下:

當(dāng)這兩個(gè)判斷 B1,B2 同時(shí)為 false捌浩,表示同步隊(duì)列有排隊(duì)線程放刨,并且同步隊(duì)列里排隊(duì)最靠前都線程就是當(dāng)前線程,這個(gè)時(shí)候也就 不需要排隊(duì)尸饺, 直接獲取进统。

B1 返回 true,這個(gè)時(shí)候同步隊(duì)列正處在初始化過程中侵佃,此時(shí)觸發(fā)了條件組 B 的短路邏輯麻昼。整個(gè)條件組 B 返回 true。說明已經(jīng)有其他線程在當(dāng)前線程之前爭用鎖了馋辈,那么當(dāng)前線程 需要排隊(duì) 。整個(gè)判斷邏輯返回 false倍谜。

B1 返回 false迈螟,B2 返回 true。表示同步隊(duì)列正在初始化過程中尔崔,并且排隊(duì)等待的下一個(gè)線程不是當(dāng)前線程答毫,那當(dāng)前線程依舊需要 加入排隊(duì)隊(duì)列 等候。

2.3 unlock 流程

公平鎖和非公平鎖的釋放鎖流程都是一樣的季春。當(dāng)我們調(diào)用

reentrantLock.unlock()

方法洗搂,debug 源代碼,可以看到還是使用了實(shí)現(xiàn)了 AQS 內(nèi)部類的成員變量的釋放鎖方法载弄。

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

而對應(yīng)的 release 方法的代碼如下耘拇,這個(gè)模板方法依然是在 AQS 同步器內(nèi)。

    public final boolean release(int arg) {
        // 嘗試釋放鎖
        if (tryRelease(arg)) {
            // 獲取頭節(jié)點(diǎn)
            Node h = head;
            // 頭節(jié)點(diǎn)不為空且頭節(jié)點(diǎn)的節(jié)點(diǎn)狀態(tài)不為0(不為0表示這個(gè)節(jié)點(diǎn)不是初始化虛擬節(jié)點(diǎn))
            if (h != null && h.waitStatus != 0)
                // 修改節(jié)點(diǎn) status 字段并喚醒等待線程
                unparkSuccessor(h);
            return true;
        }
        // 釋放鎖失敗宇攻,返回 false
        return false;
    }

tryRelease 方法和之前的 tryAcquire 方法一下惫叛,都是需要 AQS 同步器的實(shí)現(xiàn)類自己編寫的部分。

java.util.concurrent.locks.ReentrantLock.Sync#tryRelease

    // 內(nèi)部類實(shí)現(xiàn)的 - 嘗試釋放鎖方法逞刷,注意傳入的 releases 值為 1
    protected final boolean tryRelease(int releases) {
        // 獲取當(dāng)前 state 值嘉涌,然后減 1妻熊,得到一個(gè)釋放鎖之后 state 的期望值
        int c = getState() - releases;
        // 檢查釋放鎖線程和加鎖線程是不是同一個(gè)線程
        if (Thread.currentThread() != getExclusiveOwnerThread())
            // 不是的話,直接拋出異常
            throw new IllegalMonitorStateException();
        boolean free = false;
        // 如果 state 期望值為 0仑最,表示沒有重入加鎖扔役,現(xiàn)在可以直接釋放鎖
        if (c == 0) {
            // 注意只有當(dāng) state 計(jì)數(shù)值為 0 的時(shí)候,才能釋放鎖警医,否則表示之前同一個(gè)線程有重入加鎖操作
            free = true;
            // 取消獨(dú)占線程的引用
            setExclusiveOwnerThread(null);
        }
        // 更新 state 值
        setState(c);
        // 返回釋放鎖標(biāo)識位
        return free;
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末亿胸,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子法严,更是在濱河造成了極大的恐慌损敷,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件深啤,死亡現(xiàn)場離奇詭異拗馒,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)溯街,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進(jìn)店門诱桂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人呈昔,你說我怎么就攤上這事挥等。” “怎么了?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵扮念,是天一觀的道長梁沧。 經(jīng)常有香客問我,道長辞槐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任粘室,我火速辦了婚禮榄檬,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘衔统。我一直安慰自己鹿榜,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布锦爵。 她就那樣靜靜地躺著舱殿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪棉浸。 梳的紋絲不亂的頭發(fā)上怀薛,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天,我揣著相機(jī)與錄音迷郑,去河邊找鬼枝恋。 笑死创倔,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的焚碌。 我是一名探鬼主播畦攘,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼十电!你這毒婦竟也來了知押?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤鹃骂,失蹤者是張志新(化名)和其女友劉穎台盯,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體畏线,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡静盅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了寝殴。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蒿叠。...
    茶點(diǎn)故事閱讀 38,622評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蚣常,靈堂內(nèi)的尸體忽然破棺而出市咽,到底是詐尸還是另有隱情,我是刑警寧澤抵蚊,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布施绎,位于F島的核電站,受9級特大地震影響贞绳,放射性物質(zhì)發(fā)生泄漏粘姜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一熔酷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧豺裆,春花似錦拒秘、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蔑歌,卻和暖如春羹应,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背次屠。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工园匹, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留雳刺,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓裸违,卻偏偏與公主長得像掖桦,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子供汛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評論 2 348

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