ReentrantLock源碼解析(加鎖)

初識(shí)

ReentrantLock在并發(fā)場(chǎng)景下經(jīng)常用到觅捆,非常重要铺遂。它是對(duì)lock接口實(shí)現(xiàn)的可重入鎖译秦,保證多線(xiàn)程安全满着。作用是對(duì)線(xiàn)程進(jìn)行加鎖叭披,并且鎖可重入袄简,即一個(gè)線(xiàn)程獲取鎖后可重復(fù)獲取鎖而不會(huì)發(fā)生阻塞歹鱼,其定義的內(nèi)部類(lèi)Sync繼承AQS抽象類(lèi)酪刀。通過(guò)AQS抽象類(lèi)實(shí)現(xiàn)大部分的功能谈喳,小部分功能由Sync類(lèi)實(shí)現(xiàn)册烈,如AQS類(lèi)里的tryAcquire()由子類(lèi)nonfairTryAcquire()實(shí)現(xiàn)等等

基本使用

public static ReentrantLock lock = new ReentrantLock();

private static void add() {
        try {
            lock.lock();
            count++;
        }finally {
            lock.unlock();
        }
    }

使用new ReentrantLock即可進(jìn)行實(shí)例,對(duì)需要線(xiàn)程安全部分首尾調(diào)用lock()進(jìn)行加鎖,業(yè)務(wù)代碼執(zhí)行完調(diào)用lock.unlock()釋放鎖婿禽,最好是在finally中調(diào)用解鎖赏僧,以防業(yè)務(wù)代碼出現(xiàn)問(wèn)題,鎖不釋放帶來(lái)的死鎖問(wèn)題谈宛。

源碼解析

1.核心是AQS這個(gè)類(lèi)次哈,構(gòu)造一個(gè)CLH隊(duì)列,結(jié)構(gòu)如圖

16149389980022.jpg

2.ReentrantLock實(shí)現(xiàn)了Lock吆录,以及Serializable接口窑滞,提供了兩個(gè)構(gòu)造方法,其中無(wú)參構(gòu)造即實(shí)例一個(gè)非公平鎖恢筝,有參構(gòu)造方法里如果是true則構(gòu)造公平鎖

非公平鎖哀卫,當(dāng)前線(xiàn)程釋放鎖后,后續(xù)線(xiàn)程無(wú)需順序競(jìng)爭(zhēng)鎖撬槽。
公平鎖此改,當(dāng)前線(xiàn)程釋放鎖后,后續(xù)線(xiàn)程按創(chuàng)建時(shí)間競(jìng)爭(zhēng)鎖侄柔。

/**
     * Creates an instance of {@code ReentrantLock}.
     * This is equivalent to using {@code ReentrantLock(false)}.
     */
    public ReentrantLock() {
        sync = new NonfairSync();
    }

    /**
     * Creates an instance of {@code ReentrantLock} with the
     * given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }


3.當(dāng)直接new一個(gè)lock實(shí)例即創(chuàng)建一個(gè)非公平鎖

/**
     * Sync object for non-fair locks
     */
    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);
        }
    }

NonfairSync類(lèi)是Sync抽象類(lèi)的一個(gè)子類(lèi)共啃,Sync下有兩個(gè)子類(lèi)占调,分別對(duì)應(yīng)公平和非公平,同時(shí)繼承父類(lèi)AbstractQueuedSynchronizer也就是AQS移剪,AQS類(lèi)完成加鎖以及阻塞隊(duì)列構(gòu)建和運(yùn)轉(zhuǎn)

關(guān)系圖


16140696650757.jpg

Sync抽象類(lèi)里定義的幾個(gè)方法究珊,

16140697405113.jpg

其中l(wèi)ock方法由兩個(gè)類(lèi)子類(lèi)FairSync和NonFairSync實(shí)現(xiàn),
nonfairRtyAcquire方法即為獲取鎖的實(shí)現(xiàn)被兩個(gè)子類(lèi)調(diào)用纵苛,后面再說(shuō)剿涮。

4.NonfairSync子類(lèi)的lock()方法實(shí)現(xiàn)(非公平鎖)

final void lock() {
            //4.1.首先邏輯分支語(yǔ)句,第一個(gè)分支使用Sync的父類(lèi)AQS類(lèi)里的compareAndSetState()方法來(lái)獲取鎖攻人。
            //AQS類(lèi)里定義一個(gè)volatile修飾的變量state;用來(lái)標(biāo)識(shí)當(dāng)前鎖是否被占用取试,當(dāng)沒(méi)有其他線(xiàn)程獲取鎖時(shí)將state值變?yōu)?,并且調(diào)用AQS的setExclusiveOwnerThread()方法將線(xiàn)程所有者設(shè)置為當(dāng)前線(xiàn)程怀吻。
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
            //4.2.如果獲取鎖沒(méi)有成功調(diào)用AQS的acquire(1)方法瞬浓。
                acquire(1);
        }


5.繼上一步調(diào)用AQS的acquire(1)方法,邏輯控制語(yǔ)句里有兩個(gè)部分烙博,都為true時(shí)瑟蜈,即線(xiàn)程已經(jīng)經(jīng)歷過(guò)一個(gè)完整的掛起到喚醒過(guò)程,然后再調(diào)用AQS的selfInterrupt()方法對(duì)當(dāng)前線(xiàn)程進(jìn)行標(biāo)識(shí)中斷(其實(shí)是對(duì)下面acquireQueued方法的一個(gè)中斷補(bǔ)償)渣窜,具體何時(shí)中斷由線(xiàn)程自己決定铺根。

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


5.1.第一部分:AQS類(lèi)里的tryAcquire()方法調(diào)用子類(lèi)Sycn的nonfairTryAcquire()方法,入?yún)?乔宿。

nonfairTryAcquire()方法有三個(gè)分支位迂。

final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            //第一個(gè)分支,依舊是使用cas原理獲取鎖详瑞,如果獲取成功設(shè)置所屬線(xiàn)程掂林,返回true
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //第二個(gè)分支,則是ReenTrantLock的可重入性坝橡,即如果當(dāng)前線(xiàn)程和所屬線(xiàn)程一致泻帮,則將state值加1并且返回true。
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            //第三個(gè)分支计寇,當(dāng)上面都不能成立直接返回false锣杂。
            return false;
        }


5.2.第二部分:acquireQueued(addWaiter(Node.EXCLUSIVE), arg)首先調(diào)用addWaiter()方法,將當(dāng)前線(xiàn)程封裝成node加入到隊(duì)列中
private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // 判斷尾結(jié)點(diǎn)是否存在番宁,如果存在將新節(jié)點(diǎn)插入到尾結(jié)點(diǎn)元莫,將尾結(jié)點(diǎn)指向修改為新節(jié)點(diǎn)。
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //如果不存在調(diào)用enq(node)方法
        enq(node);
        return node;
    }
    //將node設(shè)置為tail尾結(jié)點(diǎn)蝶押,此時(shí)head頭節(jié)點(diǎn)是null
    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

里面addwaiter方法執(zhí)行完踱蠢,接下來(lái)是acquireQueued(node,1)方法

final boolean acquireQueued(final Node node, int arg) {
        //失敗標(biāo)記
        boolean failed = true;
        try {
            //中斷標(biāo)記
            boolean interrupted = false;
            //自旋獲取鎖,失敗則掛起嘗試獲取鎖的線(xiàn)程(我稱(chēng)之為競(jìng)爭(zhēng)線(xiàn)程)棋电,直到被喚醒
            for (;;) {
                //獲取前驅(qū)節(jié)點(diǎn)
                final Node p = node.predecessor();
                //如果前驅(qū)節(jié)點(diǎn)是頭節(jié)點(diǎn)茎截,則嘗試獲取鎖
                if (p == head && tryAcquire(arg)) {
                    //將當(dāng)前節(jié)點(diǎn)設(shè)置為頭節(jié)點(diǎn)
                    setHead(node);
                    //釋放內(nèi)存苇侵,幫助垃圾回收
                    p.next = null; // help GC
                    failed = false;
                    //競(jìng)爭(zhēng)線(xiàn)程獲取鎖后,返回false企锌,結(jié)束循環(huán)
                    return interrupted;
                }
                //下面這段主要是競(jìng)爭(zhēng)線(xiàn)程獲取鎖失敗時(shí)執(zhí)行衅檀,if邏輯里有兩個(gè)重要方法主要實(shí)現(xiàn)線(xiàn)程阻塞,在5.2.x里分析
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
5.2.1.shouldParkAfterFailedAcquire()方法主要是用來(lái)修正當(dāng)前節(jié)點(diǎn)以及前驅(qū)節(jié)點(diǎn)的狀態(tài)霎俩,并判斷當(dāng)前node是否可以進(jìn)行安全阻塞。如果可以安全阻塞則返回true繼續(xù)調(diào)用parkAndCheckInterrupt()進(jìn)行阻塞中斷沉眶。
volatile int waitStatus; //node節(jié)點(diǎn)狀態(tài)打却,默認(rèn)是0

static final int CANCELLED =  1 // waitStatus值,指示線(xiàn)程已取消
static final int SIGNAL    = -1 // waitStatus值谎倔,指示后續(xù)線(xiàn)程需要喚醒
static final int CONDITION = -2 // waitStatus值柳击,指示線(xiàn)程正在等待條件
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        
        //如果前驅(qū)節(jié)點(diǎn)的狀態(tài)是-1,返回true片习,表示當(dāng)前node可以?huà)炱鸢齐龋瓷厦?.2可調(diào)用parkAndCheckInterrupt()
        if (ws == Node.SIGNAL)
            return true;
            
        //如果前驅(qū)節(jié)點(diǎn)狀態(tài)>0,則表示前驅(qū)節(jié)點(diǎn)已經(jīng)取消藕咏,下面do while循環(huán)找到前驅(qū)節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)状知,并將它設(shè)置為node的前驅(qū)節(jié)點(diǎn)
        if (ws > 0) {
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
        //如果不等于-1,且>0,則將前驅(qū)節(jié)點(diǎn)的狀態(tài)改為-1孽查。這段方法第一次進(jìn)來(lái)會(huì)執(zhí)行此處饥悴。注意此時(shí)并沒(méi)有去返回true來(lái)掛起當(dāng)前node,而是返回false盲再。
        //因?yàn)榍膀?qū)節(jié)點(diǎn)狀態(tài)默認(rèn)是0西设。此方法外部自旋第二次循環(huán)才走第一個(gè)if返回true,然后開(kāi)始調(diào)用parkAndCheckInterrupt()掛起當(dāng)前node答朋,即掛起競(jìng)爭(zhēng)線(xiàn)程
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
5.2.2.parkAndCheckInterrupt()只做兩件事 1掛起 2檢查中斷狀態(tài)贷揽,這里特別說(shuō)明下,線(xiàn)程掛起和線(xiàn)程中斷不是一回事梦碗,此方法掛起線(xiàn)程后禽绪,代碼將不再繼續(xù)執(zhí)行,直到線(xiàn)程被中斷叉弦,或者被其他線(xiàn)程進(jìn)行unpark喚醒丐一,才能繼續(xù)return。
private final boolean parkAndCheckInterrupt() {
        //掛起競(jìng)爭(zhēng)線(xiàn)程淹冰。
        LockSupport.park(this);
        //如果是被unpark喚醒库车,即返回false,如果是被中斷喚醒樱拴,返回true柠衍,重置競(jìng)爭(zhēng)線(xiàn)程中斷狀態(tài)為false洋满。
        return Thread.interrupted();
    }



繼續(xù)parkAndCheckInterrupt()返回后進(jìn)行分析。兩種情況:
第一種情況珍坊,通過(guò)unpark方法喚醒牺勾,return Thread.interrupted() => false,進(jìn)入acquireQueued()的自旋中阵漏,進(jìn)行再次獲取鎖操作后驻民。此時(shí)return的interrupted標(biāo)識(shí)還是false,結(jié)束acquireQueued()后履怯,第5步分析的selfInterrupt()的邏輯判斷不成立回还,所以是不執(zhí)行的,即沒(méi)必要進(jìn)行中斷補(bǔ)償叹洲。
第二種情況柠硕,通過(guò)中斷喚醒,return Thread.interrupted() => true运提,進(jìn)入acquireQueued()的自旋中蝗柔,進(jìn)行再次獲取鎖操作后,此時(shí)interrupted = true民泵,第5步分析的selfInterrupt()的邏輯判斷成立癣丧,進(jìn)行中斷補(bǔ)償。

總結(jié):到此線(xiàn)程的加鎖流程已經(jīng)分析完栈妆,如果競(jìng)爭(zhēng)線(xiàn)程執(zhí)行了最后一步parkAndCheckInterrupt()方法坎缭,則進(jìn)入掛起狀態(tài),等待前面擁有鎖的線(xiàn)程釋放鎖后签钩,競(jìng)爭(zhēng)線(xiàn)程被喚醒掏呼,再次走一遍流程去嘗試獲取鎖,釋放鎖并喚醒等待線(xiàn)程在下一篇文章里再做源碼解析铅檩。

----------------- 文章如有問(wèn)題憎夷,請(qǐng)下方回復(fù)指出,感謝查閱?? -----------------

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末昧旨,一起剝皮案震驚了整個(gè)濱河市拾给,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌兔沃,老刑警劉巖蒋得,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異乒疏,居然都是意外死亡额衙,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)窍侧,“玉大人县踢,你說(shuō)我怎么就攤上這事∥凹” “怎么了硼啤?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)斧账。 經(jīng)常有香客問(wèn)我谴返,道長(zhǎng),這世上最難降的妖魔是什么咧织? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任亏镰,我火速辦了婚禮,結(jié)果婚禮上拯爽,老公的妹妹穿的比我還像新娘。我一直安慰自己钧忽,他們只是感情好毯炮,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著耸黑,像睡著了一般桃煎。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上大刊,一...
    開(kāi)封第一講書(shū)人閱讀 49,760評(píng)論 1 289
  • 那天为迈,我揣著相機(jī)與錄音,去河邊找鬼缺菌。 笑死葫辐,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的伴郁。 我是一名探鬼主播耿战,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼焊傅!你這毒婦竟也來(lái)了剂陡?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤狐胎,失蹤者是張志新(化名)和其女友劉穎鸭栖,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體握巢,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡晕鹊,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捏题。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡玻褪,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出公荧,到底是詐尸還是另有隱情带射,我是刑警寧澤,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布循狰,位于F島的核電站窟社,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏绪钥。R本人自食惡果不足惜灿里,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望程腹。 院中可真熱鬧匣吊,春花似錦、人聲如沸寸潦。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)见转。三九已至命雀,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間斩箫,已是汗流浹背吏砂。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留乘客,地道東北人狐血。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像易核,于是被迫代替她去往敵國(guó)和親氛雪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

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

  • 看這部分的前提是大家已經(jīng)看過(guò)AbstractQueuedSynchronizer這個(gè)類(lèi)耸成,知道它是個(gè)啥了哈报亩,如果不知...
    idolice24閱讀 282評(píng)論 0 0
  • 在介紹ReentrantLock之前我們先介紹下 AQS AQS 是什么 在 Lock 中,用到了一個(gè)同步隊(duì)列 A...
    阿粒_lxf閱讀 285評(píng)論 0 0
  • J.U.C Java.util.concurrent 是在并發(fā)編程中比較常用的工具類(lèi)井氢,里面包含很多用來(lái)在并發(fā)場(chǎng)景中...
    WEIJAVA閱讀 504評(píng)論 0 6
  • 1 簡(jiǎn)言 ReentrantLock:重入鎖弦追,是指一個(gè)線(xiàn)程獲取鎖之后再?lài)L試獲取鎖時(shí)會(huì)自動(dòng)獲取鎖。 Reentran...
    LZhan閱讀 338評(píng)論 0 0
  • 前言 心血來(lái)潮想到要做一些源碼分析花竞,想想從ReentrantLock類(lèi)開(kāi)始挺合適的劲件,就嘗試著先寫(xiě)一篇吧掸哑,廢話(huà)不多說(shuō)...
    海濤_meteor閱讀 293評(píng)論 0 1