ReentrantLock 與 AQS 源碼分析

ReentrantLock 與 AQS 源碼分析

1. 基本結(jié)構(gòu)

?? 重入鎖 ReetrantLock盖袭,JDK 1.5新增的類(lèi)岖沛,作用與synchronized關(guān)鍵字相當(dāng)昭殉,但比synchronized更加靈活胧谈。ReetrantLock本身也是一種支持重進(jìn)入的鎖物赶,即該鎖可以支持一個(gè)線程對(duì)資源重復(fù)加鎖,但是加鎖多少次较坛,就必須解鎖多少次印蔗,這樣才可以成功釋放鎖。

1. 繼承

沒(méi)有繼承任何類(lèi)丑勤,因?yàn)楹芏嗖僮鞫际褂昧私M合完成华嘹。

2. 實(shí)現(xiàn)

Lock, java.io.Serializable
??這里著重介紹一下 Lock 接口,接口定義了幾個(gè)必要的方法确封,也是在 ReentrantLock 中的重點(diǎn)需要分析的方法除呵。
?? 三類(lèi)方法:獲取鎖、釋放鎖爪喘、獲取條件颜曾。

public interface Lock {
    // 阻塞獲取鎖,如果獲取不到鎖就一直等待
    void lock();
    // 可中斷獲取鎖秉剑,在獲取鎖的過(guò)程可以被中斷泛豪,但是 Synchronized 是不可以
    void lockInterruptibly() throws InterruptedException;
    // 非阻塞獲取鎖,沒(méi)有獲取到鎖立即返回
    boolean tryLock();
    // 超時(shí)獲取鎖侦鹏,沒(méi)獲取到鎖等待一段時(shí)間
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    // 解鎖
    void unlock();
    // 等待喚醒機(jī)制的條件
    Condition newCondition();
}

從上面可以看到 Synchronized 和 Lock 的一些重要區(qū)別:

  1. Lock 的獲取鎖的過(guò)程是可以中斷的诡曙,Synchronized 不可以,Synchronized 只能在 wait或同步代碼塊執(zhí)行過(guò)程中才可以被中斷略水。

  2. 由于 Lock 顯示的加鎖价卤,鎖可以橫跨幾個(gè)方法,也就是臨界區(qū)的位置可以更加自由渊涝。

  3. Lock 支持超時(shí)獲取鎖慎璧。

  4. 后面會(huì)看到 Lock 還支持公平及非公平鎖。

  5. 綁定多個(gè) Condition 條件

3. 主要字段

??很好跨释,這個(gè)類(lèi)的字段非常的少胸私,真正起作用的字段只有一個(gè) “鎖” 字段。

    // 同步鎖
    private final Sync sync;

?? 這個(gè)鎖(Sync)是一個(gè)繼承自 AQS 的抽象內(nèi)部類(lèi)鳖谈,說(shuō)明一下 AQS (AbstractQueuedSynchronizer) 一般被稱(chēng)為隊(duì)列同步器岁疼,他是并發(fā)包中的核心組件,絕大多數(shù)鎖機(jī)制都是采用的這個(gè)類(lèi)來(lái)實(shí)現(xiàn)的缆娃。雖然看到他是一個(gè)抽象類(lèi)捷绒,但是你會(huì)發(fā)現(xiàn)里面沒(méi)有一個(gè)方法是抽象方法瑰排,他實(shí)現(xiàn)了鎖機(jī)制中的必要的通用的方法,待會(huì)會(huì)專(zhuān)門(mén)講這個(gè)類(lèi)疙驾。不然 ReentrantLock 沒(méi)辦法說(shuō)凶伙,ReentrantLock 里面的鎖操作都是依賴(lài)于 AQS郭毕。

?? 然后這個(gè)鎖是有兩個(gè)子類(lèi)它碎,分別是 NonfairSyncFairSync 從名字上也可以看出這兩個(gè)類(lèi)分別代表了 公平鎖非公平鎖 。何為鎖的公平性显押? 實(shí)際上就是新來(lái)的線程需要征用鎖必須要要等到先于他到達(dá)的線程獲取并釋放鎖扳肛。也就是獲取鎖的過(guò)程是按照下來(lái)后到的順序進(jìn)行的,反之就稱(chēng)為非公平鎖乘碑。后面我們會(huì)看到其實(shí)這兩種鎖不同就在于非公平鎖在新線程創(chuàng)建后首先會(huì)直接進(jìn)行鎖的獲取挖息,如果沒(méi)有獲取到會(huì)進(jìn)行一段時(shí)間的自旋,始終沒(méi)獲取到鎖才進(jìn)行等待狀態(tài)兽肤。

?? 一般而言套腹,公平鎖開(kāi)銷(xiāo)比非公平鎖大,這也是比較符合我們的直觀感受资铡。公平鎖是需要進(jìn)行排隊(duì)的电禀,但在某些場(chǎng)景下,可能更注重時(shí)間先后順序笤休,那么公平鎖自然是很好的選擇尖飞。

?? 好總結(jié)一下,在 ReentrantLock 中只維護(hù)了一個(gè) “鎖” 變量店雅,這個(gè)鎖是繼承了 AQS 同步器政基,然后這個(gè)鎖又有兩種派生的鎖:公平鎖,非公平鎖闹啦。那么 ReentrantLock 實(shí)現(xiàn)其實(shí)就有兩種方式:公平鎖沮明,非公平鎖。

4. 主要方法概覽

  1. ctor-2
  2. lock
  3. lockInterruptibly
  4. tryLock
  5. tryLock(time)
  6. unlock
  7. newCondition

2. 基礎(chǔ)并發(fā)組件 AQS

1. 基本字段

1. 重要字段

?? AQS 是維護(hù)了一個(gè)同步隊(duì)列(雙向鏈表)窍奋,這個(gè)隊(duì)列里面線程都是需要競(jìng)爭(zhēng)鎖的荐健,沒(méi)有競(jìng)爭(zhēng)到的就在同步隊(duì)列中等待。headtail 就指向隊(duì)列的首尾费变。state 是一個(gè)標(biāo)志字段摧扇,表示當(dāng)前有多少線程在臨界區(qū)。一般來(lái)說(shuō) state 只能是 0 或 1 但是由于鎖是可重入的挚歧,所以也有大于 1 的情況扛稽。

?? 除了一個(gè)同步隊(duì)列還有 0~n 個(gè)等待隊(duì)列,等待隊(duì)列就是調(diào)用了 await 方法的線程滑负,會(huì)被掛到調(diào)用了 awaitcondition 上面的等待隊(duì)列在张,所以有多少個(gè) condition 就有多少等待隊(duì)列用含。

    //同步隊(duì)列頭指針
    private transient volatile Node head;
    // 同步隊(duì)列尾指針
    private transient volatile Node tail;
    // 狀態(tài)標(biāo)志,0 則沒(méi)有線程在臨界區(qū)帮匾,非零表示有 state 個(gè)線程在臨界區(qū)(由于鎖可重入)
    private volatile int state;

2. Node 節(jié)點(diǎn)

??Node 節(jié)點(diǎn)也就是上文所提到的 同步隊(duì)列等待隊(duì)列 中的元素啄骇,注意兩個(gè)隊(duì)列之間的元素類(lèi)型是一樣的因?yàn)樗麄冎g會(huì)有相互移動(dòng)轉(zhuǎn)換的動(dòng)作,這兩個(gè)隊(duì)列中的元素自然是線程瘟斜,為了方便查找和表示 AQS 將線程封裝到了 Node 節(jié)點(diǎn)中缸夹,構(gòu)成雙向隊(duì)列。

static final class Node {
        // 共享非 null/獨(dú)占為 null  
        static final Node SHARED = new Node();
        static final Node EXCLUSIVE = null;

        /**
         * 線程狀態(tài)
         */
        static final int CANCELLED =  1;
        static final int SIGNAL    = -1;
        static final int CONDITION = -2;
        static final int PROPAGATE = -3;
        volatile int waitStatus;
       // 雙向鏈表  這兩個(gè)指針用于同步隊(duì)列構(gòu)建鏈表使用的   下面還有一個(gè) nextWaiter 是用來(lái)構(gòu)建等待單鏈表隊(duì)列
        volatile Node prev;
        volatile Node next;
        // 線程
        volatile Thread thread;
        // 等待隊(duì)列單鏈表
        Node nextWaiter;

        /**
         * Returns true if node is waiting in shared mode.
         */
        final boolean isShared() {
            return nextWaiter == SHARED;
        }

    }

?? 可以看到上面有一個(gè) waitStatus 屬性螺句,代表了線程當(dāng)前的狀態(tài)虽惭,狀態(tài)標(biāo)識(shí)就是那些常量。具體如下:

  1. SIGNAL: 正在執(zhí)行的線程結(jié)束釋放鎖或者被取消執(zhí)行蛇尚,他必須喚醒后續(xù)的狀態(tài)為 SIGNAL 節(jié)點(diǎn)

  2. CANCELLED: 在同步隊(duì)列中等待的線程等待超時(shí)或被中斷芽唇,需要從同步隊(duì)列中取消該Node的結(jié)點(diǎn), 其結(jié)點(diǎn)的waitStatus為CANCELLED取劫,即結(jié)束狀態(tài)匆笤,進(jìn)入該狀態(tài)后的結(jié)點(diǎn)將不會(huì)再變化。

  3. CONDITION: 該標(biāo)識(shí)的結(jié)點(diǎn)處于等待隊(duì)列中(不是同步隊(duì)列)谱邪,結(jié)點(diǎn)的線程等待在Condition上炮捧,當(dāng)其他線程調(diào)用了Condition的signal()方法后,CONDITION狀態(tài)的結(jié)點(diǎn)將從等待隊(duì)列轉(zhuǎn)移到同步隊(duì)列中虾标,等待獲取同步鎖寓盗。

  4. PROPAGATE:在共享模式中,該狀態(tài)標(biāo)識(shí)結(jié)點(diǎn)的線程處于可運(yùn)行狀態(tài)璧函。

  5. 0:代表初始化狀態(tài)傀蚌。

?? 可以看到,Node 里面的主要字段就是一個(gè)狀態(tài)標(biāo)志位蘸吓、一個(gè)線程的引用善炫、用于構(gòu)建鏈表的指針。注意库继,有三個(gè)指針箩艺,其中前兩個(gè) nextpre 是用來(lái)構(gòu)建同步隊(duì)列的(雙向鏈表),后面 nextWaiter 是用來(lái)構(gòu)建等待隊(duì)列宪萄。所以說(shuō)雖然同步隊(duì)列和等待隊(duì)列使用的同一個(gè)數(shù)據(jù)類(lèi)型艺谆,數(shù)據(jù)結(jié)構(gòu)是不同的,并且在后面我們會(huì)看到等待隊(duì)列中的節(jié)點(diǎn)只有兩種狀態(tài) ConditionCANCELLED 前者表示線程已結(jié)束需要從等待隊(duì)列中移除拜英,后者表示條件結(jié)點(diǎn)等待被喚醒静汤。

??下面畫(huà)圖說(shuō)明一下同步隊(duì)列和等待隊(duì)列的情況。
等待隊(duì)列


image

同步隊(duì)列


image

3. ConditionObject

?? 這個(gè)內(nèi)部類(lèi)是等待喚醒機(jī)制的核心,在他上面綁定了一個(gè)等待隊(duì)列虫给。在這個(gè)類(lèi)中使用了兩個(gè)指針( firstWaiter/lastWaiter )指向隊(duì)列的首尾藤抡。這里主要看一下 awaitsignalsignalAll 方法抹估。

  1. await
    ?? 當(dāng)一個(gè)線程調(diào)用了await()相關(guān)的方法缠黍,那么首先構(gòu)建一個(gè)Node節(jié)點(diǎn)封裝當(dāng)前線程的相關(guān)信息加入到等待隊(duì)列中進(jìn)行等待,并釋放鎖直到被喚醒(移動(dòng)到同步隊(duì)列)药蜻、中斷瓷式、超時(shí)才被隊(duì)列中移出。被喚醒后的第一件事是搶鎖和檢查是否被中斷谷暮,然后才是移除隊(duì)列蒿往。被喚醒時(shí)候的狀態(tài)應(yīng)該為 SIGNAL ,而在方法中執(zhí)行的移除隊(duì)列的操作就是移除狀態(tài)非 Condition 的節(jié)點(diǎn)湿弦。
public final void await() throws InterruptedException {
            // 等待可中斷
            if (Thread.interrupted())
                throw new InterruptedException();
            // 加入等待隊(duì)列, new 新的 Node 做一個(gè)尾插入
            Node node = addConditionWaiter();
            // 釋放當(dāng)前線程的鎖腾夯,失敗則將當(dāng)前線程設(shè)置為取消狀態(tài)
            int savedState = fullyRelease(node);

            int interruptMode = 0;
            // 如果沒(méi)在同步隊(duì)列就讓線程等待也就是看是否被喚醒
            // 如果有中斷或者被喚醒那么退出循環(huán)
            while (!isOnSyncQueue(node)) {
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            // 運(yùn)行到此處說(shuō)明已經(jīng)被喚醒了颊埃,因?yàn)榻Y(jié)束了循環(huán)
            // 喚醒后,首先自旋一下獲取鎖蝶俱,同時(shí)判斷是否中斷
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            // 清理隊(duì)列中狀態(tài)不是 Condition 的的任務(wù)班利,包括被喚醒的 SIGNAL 和 被取消的 CANCELLED
            if (node.nextWaiter != null)
                unlinkCancelledWaiters();
            //被中斷 拋異常
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }
  1. signal/doSignal/signalAll
    ?? 執(zhí)行 signal 首先進(jìn)行鎖的判斷,如果沒(méi)有獲取到獨(dú)占鎖就直接拋出異常榨呆。這也就是為什么只有擁有鎖的線程才能執(zhí)行 signal 罗标,然后獲取等待隊(duì)列中的第一個(gè)節(jié)點(diǎn)執(zhí)行 doSignal。
        public final void signal() {
            // 獲取獨(dú)占鎖
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            // 喚醒等待隊(duì)里中的第一個(gè)線程
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }

?? doSignal 方法主要就干了三個(gè)事 :

  1. 將被喚醒的節(jié)點(diǎn)從等待隊(duì)列中移除(while 循環(huán)體)积蜻,如果被喚醒的節(jié)點(diǎn)被取消了就繼續(xù)喚醒后面的節(jié)點(diǎn)(transferForSignal 返回 false)
  2. 否則把這個(gè)節(jié)點(diǎn)加入到同步隊(duì)列 ( enq 方法 )
  3. 當(dāng)同步隊(duì)列中當(dāng)前節(jié)點(diǎn)的前驅(qū)被取消或者沒(méi)辦法喚醒時(shí)則喚醒這個(gè)線程 ( unpark )闯割,這時(shí)候調(diào)用了 unpark 正好和 await 中的 park 相對(duì)應(yīng)使得 await 的線程被喚醒,接著執(zhí)行循環(huán)體判斷自己已經(jīng)被移入到同步隊(duì)列了竿拆,接著就可以執(zhí)行后面的獲取鎖的操作宙拉。
 private void doSignal(Node first) {
            do {
                // 頭指針指向喚醒節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn),并順便判斷等待隊(duì)列是否空
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                // 解除引用
                first.nextWaiter = null;
            } while (!transferForSignal(first) && (first = firstWaiter) != null); //移入同步隊(duì)列失敗則繼續(xù)喚醒下一個(gè)線程丙笋,否則喚醒成功
            // 喚醒成功的線程不一定馬上能開(kāi)始執(zhí)行谢澈,只有在前驅(qū)節(jié)點(diǎn)被取消或者沒(méi)辦法被喚醒時(shí)
   }
   
   
   //  將節(jié)點(diǎn)從等待隊(duì)列移動(dòng)到同步隊(duì)列   成功返回 true 失敗 false
    final boolean transferForSignal(Node node) {
        // 在等待隊(duì)列中的節(jié)點(diǎn)只有 condition 和 cancelled 兩種狀態(tài),如果狀態(tài)更新失敗說(shuō)明任務(wù)被取消
        // 否則更新為初始狀態(tài)   直接返回的話上面的 doSignal 就會(huì)繼續(xù)喚醒后面的線程
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;
        // 把當(dāng)前節(jié)點(diǎn)加入同步隊(duì)列
        Node p = enq(node);
        // 獲取同步隊(duì)列中倒數(shù)第二個(gè)節(jié)點(diǎn)的狀態(tài)御板,當(dāng)前節(jié)點(diǎn)的前驅(qū)
        int ws = p.waitStatus;
        // 如果前驅(qū)節(jié)點(diǎn)被取消或者在設(shè)置前驅(qū)節(jié)點(diǎn)狀態(tài)為Node.SIGNAL狀態(tài)失敗時(shí)锥忿,喚醒被通知節(jié)點(diǎn)代表的線程
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }
    
    
    // 插入一個(gè)節(jié)點(diǎn)到同步隊(duì)列,如果同步隊(duì)列是空的則加入一個(gè)空節(jié)點(diǎn)做為頭結(jié)點(diǎn)
    // 死循環(huán)保證肯定能插入    返回插入節(jié)點(diǎn)的前驅(qū)
    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                // 這一步不需要 cas 是因?yàn)椴l(fā)沒(méi)關(guān)系怠肋,只是指向鏈表結(jié)尾敬鬓,不會(huì)多線程更新問(wèn)題
                node.prev = t;
                // 可能有多個(gè)線程搶
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

?? 有一個(gè)小問(wèn)題,就是在某個(gè)線程中執(zhí)行了別人的 signal 不會(huì)導(dǎo)致當(dāng)前線程立即放棄鎖,之所以會(huì)這樣正是由于 ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL) 這個(gè)判斷,即前驅(qū)線程都結(jié)束了列林。比如下面的例子:

package util.AQSTest;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

// test signal 執(zhí)行后不會(huì)導(dǎo)致當(dāng)前線程立即釋放鎖
public class AQSTest {
    static Lock lock = new ReentrantLock();
   static Condition run1Cond = lock.newCondition();
    static Condition run2Cond = lock.newCondition();

    static class Runner1 implements Runnable {
        @Override
        public void run() {
            lock.lock();
            try {
                System.out.println("runner 1 start");
                run1Cond.await(1, TimeUnit.SECONDS);
                run2Cond.signal();
                System.out.println("runner 1 exit");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }

        }
    }

    static class Runner2 implements Runnable {
        @Override
        public void run() {
            lock.lock();
            try {
                System.out.println("runner 2 start");
                run2Cond.await();
                System.out.println("runner 2 exit");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }

        }
    }
    public static void main(String[] args) {
        new Thread(new Runner1(),"runner1").start();
        new Thread(new Runner2(),"runner2").start();
    }
}

輸出的結(jié)果始終是:

runner 1 start
runner 2 start
runner 1 exit
runner 2 exit

?? 我使用了工具對(duì)上面的代碼進(jìn)行了調(diào)試瑞你,大致說(shuō)一下流程,順便用來(lái)捋一捋等待喚醒機(jī)制希痴。

?? 首先 runner1 啟動(dòng)者甲,獲取到鎖,打印出 “runner1 start” 砌创,然后調(diào)用了 await 方法虏缸,此時(shí) runner1 線程就執(zhí)行了 AQS 中的 ConditionObject 中的 await 方法,該方法首先 new 了一個(gè)新的節(jié)點(diǎn)嫩实,把 runner1 封裝到這個(gè)節(jié)點(diǎn)里面刽辙。掛在了 run1Con 的等待隊(duì)列上,然后執(zhí)行了釋放鎖并判斷中斷甲献。緊接著 runner1 線程執(zhí)行循環(huán)體判斷是否被喚醒也就是是否在同步隊(duì)列宰缤,顯然這時(shí)候不在,就直接調(diào)用了 park 方法晃洒,執(zhí)行休眠 1 秒鐘操作慨灭, park 方法是 native 方法由操作系統(tǒng)實(shí)現(xiàn)。在上面線程釋放鎖的時(shí)候執(zhí)行的操作是 fullyRelease 這個(gè)方法調(diào)用了 release 方法球及,而 release 方法中釋放了鎖之后氧骤,會(huì)檢查同步隊(duì)列中是否還有以前因?yàn)闆](méi)搶到鎖而等待的線程,如果有執(zhí)行 unparkSuccessor 也就是喚醒同步隊(duì)列中的后繼線程吃引。那么此時(shí) runner2 會(huì)被喚醒筹陵,喚醒后就去搶鎖,獲取到 lock 鎖后輸出了 “runner2 start” 镊尺,然后 runner2 線程又會(huì)因?yàn)檎{(diào)用 await 處于和 runner1 同樣的境地朦佩,也就是被放入 run2Con 的等待隊(duì)列。好鹅心!此時(shí) runner1 的超時(shí)時(shí)間到了吕粗,就會(huì)被 unpark 這個(gè) unpark 是被操作系統(tǒng)調(diào)用的,之后繼續(xù)執(zhí)行循環(huán)體發(fā)現(xiàn)超時(shí)時(shí)間小于等于 0 旭愧,則調(diào)用 transferAfterCancelledWait 里面調(diào)用了 enq 就是加入同步隊(duì)列颅筋,接著開(kāi)始競(jìng)爭(zhēng)鎖,開(kāi)始執(zhí)行 run2Con 上的 signal 此時(shí) signal 調(diào)用 doSignal 先執(zhí)行 do while 中的循環(huán)體输枯,runner2 從 run2Con 的等待隊(duì)列上移除议泵,然后執(zhí)行 transferForSignal 里面又調(diào)用了 enq 將他加入同步隊(duì)列,并返回同步隊(duì)列中的前驅(qū)桃熄,前驅(qū)節(jié)點(diǎn)狀態(tài)不是 Cancelled 或者 可以被置為 SIGNAL 則 signal 方法結(jié)束先口。接著打印了 “runner1 exit” 。接著需要執(zhí)行 finally 里面的釋放鎖的操作了,顯然 unlock 肯定調(diào)用了 release 碉京,而 release 會(huì)喚醒同步隊(duì)列中的后繼的線程厢汹,那么位于同步隊(duì)列中的 runner2 之前的 park 狀態(tài)就會(huì)被打斷,從而跳出 while 循環(huán)谐宙,執(zhí)行獲取鎖的操作烫葬。打印出 “runner2 exit” ,最后釋放鎖整個(gè)程序結(jié)束凡蜻。

?? 現(xiàn)在總算是吧 Condition 的等待喚醒機(jī)制弄清楚了搭综。也把 AQS 中的兩個(gè)內(nèi)部類(lèi)的功能都解釋完了。接下來(lái)就看 AQS 中的方法划栓。

2. 重要方法

  1. get/setState
  2. release/tryRelease/unparkSuccessor/fullyRelease
  3. acquire/tryAcquire/addWaiter/tryQueued
  4. acquireShared
  5. releaseShared

?? 這些屬于 AQS 中常用的方法兑巾,但是里面的核心方法都是模板方法,也就是說(shuō)由繼承他的子類(lèi)來(lái)實(shí)現(xiàn)忠荞,所以只能看個(gè)大概的邏輯蒋歌。一會(huì)等到講 ReentrantLock 時(shí)再詳細(xì)說(shuō)這里面的方法。

3. ReentrantLock 內(nèi)部類(lèi) Sync/fairSync/noFairSync

1. Sync

?? 這三個(gè)內(nèi)部類(lèi)實(shí)際上是繼承自 AQS 钻洒,也就是說(shuō) ReentrantLock 是采用了 AQS 作為自己的核心并發(fā)控制組件完成的一系列的鎖操作奋姿,及等待喚醒機(jī)制。

?? 首先看一下 Sync 他是后面兩個(gè)的父類(lèi)素标,他直接繼承自 AQS 。AQS 中留了幾個(gè)比較重要的模板方法 tryAcquire 萍悴、tryRelease 头遭。這個(gè)方法直接實(shí)現(xiàn)了一些在公平鎖和非公平鎖中的通用操作,也就是釋放鎖的操作 tryRelease 癣诱。

?? tryRelease 的實(shí)現(xiàn)很簡(jiǎn)單计维,主要就是依賴(lài)于 AQS 中的 state 屬性,如果state 值減去要釋放的信號(hào)量為 0 則釋放成功撕予,否則失敗鲫惶。

        // 釋放鎖的公共操作
        protected final boolean tryRelease(int releases) {
            // 釋放鎖首先就是使用 AQS 中的 state 的值減去信號(hào)量 判斷是否為0
            // 如果是 0 則表明成功釋放鎖,獨(dú)占線程設(shè)為 null实抡,否則說(shuō)明還占用鎖
            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;
        }

2. fairSync

    公平鎖執(zhí)行 lock 操作就是執(zhí)行了 AQS 中的 acquire(1) 也就是請(qǐng)求一個(gè)鎖資源。但是注意吆寨,在 AQS 中的 acquire 中的 tryAcquire 方法沒(méi)有實(shí)現(xiàn)赏淌,所以必須由當(dāng)前類(lèi)實(shí)現(xiàn)。

    在 tryAcquire 中做的事情就是看是否有代碼在臨界區(qū)啄清。沒(méi)有則還要看同步隊(duì)列中是否有線程等待六水,當(dāng)只有這一個(gè)線程在獲取鎖的時(shí)候才能正常的獲取鎖,其他情況都失敗。
// 公平鎖
    static final class FairSync extends Sync {
        final void lock() {
            acquire(1);
        }

        // 沒(méi)有代碼在臨界區(qū)或者是當(dāng)前線程的重入 則獲取成功掷贾,否則失敗
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            // 如果當(dāng)前線程在獲取鎖的過(guò)程沒(méi)有其他線程在臨界區(qū)
            if (c == 0) {
                // 如果同步隊(duì)列中沒(méi)有等待的線程睛榄,就設(shè)置 state ,并且當(dāng)前線程設(shè)為獨(dú)占線程
                if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            // 有程序在臨界區(qū)想帅,如果是當(dāng)前線程可重入场靴,加上請(qǐng)求的資源數(shù)
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            // 競(jìng)爭(zhēng)鎖失敗,因?yàn)樗枪降逆i競(jìng)爭(zhēng)
            return false;
        }
    }

3. noFairSync

同理博脑,這個(gè)方法也需要實(shí)現(xiàn) lock 和 tryAcquire 操作憎乙。在 lock 中直接判斷是否有代碼在臨界區(qū),沒(méi)有則直接獲取到鎖叉趣,與公平鎖不同的是:公平鎖還判斷了等待隊(duì)列中是否有等待的線程泞边。有在臨界區(qū)的情況時(shí)執(zhí)行 acquire 操作。同樣的疗杉,首先要執(zhí)行 tryAcquire 如果失敗阵谚,加入同步隊(duì)列并自旋獲取鎖。還是 tryAcquire 的實(shí)現(xiàn)烟具,這里又調(diào)用了 nonfairTryAcquire梢什。

    // 非公平鎖
    static final class NonfairSync extends Sync {
        final void lock() {
            // 如果沒(méi)有代碼在臨界區(qū) 直接獲取鎖,獨(dú)占
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
            // 有代碼在臨界區(qū)則執(zhí)行嘗試獲取鎖
                acquire(1);
        }

        // 和公平鎖中的 tryAcquire 一模一樣只是少了關(guān)于同步隊(duì)列中是否有等待線程的判斷
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }
    
    final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            // 沒(méi)有線程獲取鎖 直接獲取到鎖  和公平鎖中的 tryAcquire 一模一樣只是少了關(guān)于同步隊(duì)列的判斷
            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;
        }

?? 好了朝聋,現(xiàn)在我們 AQS 中的空的核心方法也被子類(lèi)實(shí)現(xiàn)了嗡午,那么現(xiàn)在 fairSync 和 noFairSync 就算是一個(gè)完整的 AQS 了。此時(shí)看一下加解鎖的流程冀痕。

只說(shuō)公平鎖荔睹,因?yàn)榉枪芥i就只是少了一個(gè)判斷。

  1. 首先 sync 調(diào)用 lock 方法言蛇,讓后 lock 調(diào)用了 AQS 的 acquire(1) 也就是獲取一個(gè)鎖資源僻他。

  2. acquire 就先調(diào)用 tryAcquire(1) 嘗試獲取鎖,這時(shí)候代碼又回調(diào)到 sync 中的實(shí)現(xiàn)的 tryAcquire 方法腊尚,這個(gè)方法先判斷鎖是否已經(jīng)被別的線程使用吨拗,然后需要確定沒(méi)有更早的線程在同步隊(duì)列等待獲取鎖,才把當(dāng)前線程設(shè)置為獨(dú)占線程婿斥,并設(shè)置 state 值獲取鎖劝篷。但是如果有代碼在臨界區(qū)需要判斷是否為當(dāng)前線程,因?yàn)殒i是可重入的受扳。如果是當(dāng)前線程則 state 加上請(qǐng)求鎖的個(gè)數(shù)携龟,返回。

  3. 這時(shí)候又回到 AQS 中勘高,如果上面嘗試獲取鎖的過(guò)程失敗峡蟋,就需要調(diào)用 addWaiter 將當(dāng)前線程封裝成一個(gè)獨(dú)占節(jié)點(diǎn)坟桅,等待狀態(tài)默認(rèn)為 0侣诵,并且返回當(dāng)前節(jié)點(diǎn)廷雅。

  4. 加入同步隊(duì)列后棚瘟,再調(diào)用 acquireQueued 方法稠鼻,當(dāng)此線程是同步隊(duì)列中等待的第一個(gè)線程則自旋嘗試獲取鎖,畢竟很可能正在執(zhí)行的線程馬上就會(huì)釋放鎖了蚯斯,再進(jìn)行休眠不合適箫攀。如果自旋獲取鎖失敗則判斷節(jié)點(diǎn)狀態(tài)是否為 SIGNAL 然后執(zhí)行等待操作抑月。

  5. 鎖獲取成功則把當(dāng)前節(jié)點(diǎn)設(shè)置為頭結(jié)點(diǎn)子漩,把 thread = null

  6. 至此豫喧,Acquire 方法執(zhí)行結(jié)束。

然后調(diào)用 unlock 方法解鎖操作幢泼。

  1. 解鎖操作就沒(méi)那么麻煩紧显,首先還是調(diào)用到了 AQS 中的 release 方法,這個(gè)方法首先嘗試解鎖當(dāng)前線程缕棵,又回調(diào)到了 sync 中的 tryRelease 孵班。

  2. tryRelease 邏輯比較簡(jiǎn)單,使用 AQS 中的 state 減去釋放的資源數(shù)招驴,等于 0 代表完全釋放篙程,否則釋放失敗。

  3. 如果 tryRelease 成功執(zhí)行就要去喚醒同步隊(duì)列中的后繼節(jié)點(diǎn)别厘,繼續(xù)執(zhí)行虱饿。

  4. 至此,release 方法執(zhí)行完畢触趴。

4. AQS 中的要方法

1. get/setState

??這兩個(gè)方法主要是對(duì) state 變量的 volatile 的讀寫(xiě)郭厌,其實(shí)里面就就是普通的 get/set 方法。但是注意的一點(diǎn)就是 state 是 volatile 的雕蔽。

    // 對(duì)狀態(tài)變量的 volatile 讀寫(xiě)
    protected final int getState() {
        return state;
    }
    protected final void setState(int newState) {
        state = newState;
    }

2. release/tryRelease/unparkSuccessor/fullyRelease

?? 這幾個(gè)方法在一起說(shuō)主要是因?yàn)樗麄冎g存在調(diào)用鏈,首先來(lái)看 release 這個(gè)方法我們?cè)谏厦嬉卜治隽吮瞿龋锩嬲{(diào)用了 tryRelease 批狐、unparkSuccessor。 也就是首先調(diào)用 tryRelease 來(lái)釋放當(dāng)前線程的鎖前塔,如果釋放成功就調(diào)用 unparkSuccessor 來(lái)喚醒同步隊(duì)列中后繼節(jié)點(diǎn)嚣艇。其中 tryRelease 是由子類(lèi)來(lái)實(shí)現(xiàn),里面的主要邏輯就是看當(dāng)前的 state 變量的值在修改過(guò)后是否為0 华弓。這里還有一個(gè) fullRelease 主要是在 ConditionObject 中調(diào)用的食零,當(dāng)執(zhí)行 await 的操作的時(shí)會(huì)執(zhí)行此方法釋放鎖。

 //  嘗試釋放鎖
    public final boolean release(int arg) {
        // 如果釋放鎖成功 喚醒同步隊(duì)列中的后繼節(jié)點(diǎn)
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
    
    // 喚醒同步隊(duì)列中的后繼節(jié)點(diǎn)
    private void unparkSuccessor(Node node) {
        // node 一般就是當(dāng)前正在運(yùn)行的線程
        int ws = node.waitStatus;
        // 當(dāng)前線程置為初始狀態(tài)   可以失敗
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);
        // 找到同步隊(duì)列中的下一個(gè)節(jié)點(diǎn)
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {  //沒(méi)有下一個(gè)節(jié)點(diǎn)或者被取消
            s = null;
            // 從后往前找第一個(gè)沒(méi)有被取消的線程
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        // 喚醒那個(gè)線程
        if (s != null)
            LockSupport.unpark(s.thread);
    }
    
    final int fullyRelease(Node node) {
        boolean failed = true;
        try {
            int savedState = getState();
            if (release(savedState)) {
                failed = false;
                return savedState;
            } else {
                throw new IllegalMonitorStateException();
            }
        } finally {
            if (failed)
                node.waitStatus = Node.CANCELLED;
        }
    }

3. acquire/tryAcquire/addWaiter/acquireQueued

這個(gè)和上面的一樣寂屏,在執(zhí)行了 acquire 后贰谣,會(huì)去調(diào)用子類(lèi)復(fù)寫(xiě)的 tryAcquire 方法娜搂,這個(gè)方法就是看有否有代碼塊在臨界區(qū),沒(méi)有的話直接獲取鎖(非公平鎖)吱抚,設(shè)置 state百宇,有的話要判斷是不是當(dāng)前線程能否進(jìn)行重入操作,否則就獲取失敗秘豹。失敗后會(huì)調(diào)用 addWaiter 携御,new 一個(gè)新的節(jié)點(diǎn)加入到同步隊(duì)列,接著調(diào)用了 acquireQueued 如果這個(gè)節(jié)點(diǎn)是同步隊(duì)列中的第一個(gè)等待的線程(但不是第一個(gè)節(jié)點(diǎn)既绕,因?yàn)榈谝粋€(gè)節(jié)點(diǎn)是 thread=null 的運(yùn)行中的線程)就自旋一段時(shí)間看能否獲取到鎖啄刹。不能則 park 等待。

// 獲取鎖
    public final void acquire(int arg) {
        // 嘗試獲取鎖 失敗則加入同步隊(duì)列 如果是同步隊(duì)列中的第一個(gè)線程就自旋獲取鎖
        // 上面的步驟的自旋獲取鎖階段凄贩,返回的是是否需要中斷誓军,所以下面就進(jìn)行 selfInterrupt
        // tryAcquire 是模板方法,因?yàn)閷?duì)于公平鎖和非公平鎖獲取鎖方式不同
        if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    
    
    // 創(chuàng)建一個(gè)節(jié)點(diǎn)放入到同步對(duì)列中   可傳入是否為獨(dú)占鎖   返回當(dāng)前節(jié)點(diǎn)
    private Node addWaiter(Node mode) {
        // 默認(rèn)的 status 是 0
        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;
            // 把 tail 設(shè)置為 node 成功說(shuō)明沒(méi)有競(jìng)爭(zhēng)
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        // 失敗則就說(shuō)明空隊(duì)列   創(chuàng)建頭結(jié)點(diǎn)
        enq(node);
        return node;
    }
    
     final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            // 自旋獲取鎖
            for (;;) {
                // 獲取前驅(qū)節(jié)點(diǎn)
                final Node p = node.predecessor();
                // 如果前驅(qū)是空的頭結(jié)點(diǎn)怎炊,那么也就是說(shuō)當(dāng)前線程就是隊(duì)列中的第一個(gè)線程 并嘗試獲取鎖  成功的話方法返回中斷情況
                if (p == head && tryAcquire(arg)) {
                    // 把當(dāng)前節(jié)點(diǎn)設(shè)置為頭結(jié)點(diǎn)  thread=null 也就可以看做當(dāng)前線程在運(yùn)行谭企,所以就不在同步隊(duì)列
                    setHead(node);
                    // gc
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                // 如果獲取鎖失敗,檢測(cè)為 SIGNAL 或者設(shè)置為 SIGNAL 然后讓此線程等待 等待操作在 parkAndCheckInterrupt 中完成
                if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            // 失敗 取消
            if (failed)
                cancelAcquire(node);
        }
    }

5. 總結(jié)

?? 其實(shí)到這里 ReentrantLock 已經(jīng)講完了评肆,因?yàn)樗讓尤空{(diào)用的是 Sync 中的方法债查,也就是全都是調(diào)用了 AQS 中的方法。而 AQS 中的大部分重要的方法都已經(jīng)看過(guò)了瓜挽。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末盹廷,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子久橙,更是在濱河造成了極大的恐慌俄占,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件淆衷,死亡現(xiàn)場(chǎng)離奇詭異缸榄,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)祝拯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)甚带,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人佳头,你說(shuō)我怎么就攤上這事鹰贵。” “怎么了康嘉?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵碉输,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我亭珍,道長(zhǎng)敷钾,這世上最難降的妖魔是什么枝哄? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮闰非,結(jié)果婚禮上膘格,老公的妹妹穿的比我還像新娘。我一直安慰自己财松,他們只是感情好瘪贱,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著辆毡,像睡著了一般菜秦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上舶掖,一...
    開(kāi)封第一講書(shū)人閱讀 49,079評(píng)論 1 285
  • 那天球昨,我揣著相機(jī)與錄音,去河邊找鬼眨攘。 笑死主慰,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的鲫售。 我是一名探鬼主播共螺,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼情竹!你這毒婦竟也來(lái)了藐不?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤秦效,失蹤者是張志新(化名)和其女友劉穎雏蛮,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體阱州,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡挑秉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了苔货。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片衷模。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蒲赂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情刁憋,我是刑警寧澤滥嘴,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站至耻,受9級(jí)特大地震影響若皱,放射性物質(zhì)發(fā)生泄漏镊叁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一走触、第九天 我趴在偏房一處隱蔽的房頂上張望晦譬。 院中可真熱鬧,春花似錦互广、人聲如沸敛腌。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)像樊。三九已至,卻和暖如春旅敷,著一層夾襖步出監(jiān)牢的瞬間生棍,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工媳谁, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留涂滴,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓晴音,卻偏偏與公主長(zhǎng)得像柔纵,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子段多,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345

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