溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

ReentrantLock源碼解析是什么

發(fā)布時間:2021-10-21 10:52:16 來源:億速云 閱讀:141 作者:柒染 欄目:大數(shù)據(jù)

ReentrantLock源碼解析是什么,相信很多沒有經(jīng)驗的人對此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個問題。

什么是可重鎖ReentrantLock?

那么什么是重入鎖呢?

重入鎖(遞歸鎖)可以理解為:同一個線程函數(shù)獲得鎖之后,內(nèi)層遞歸函數(shù)依然能夠獲取到該鎖對象的代碼,也即,在同一個線程的外層方法訪問的時候,獲取到了鎖,在進入內(nèi)層方法后能夠自動獲取到鎖。線程可以進入任何一個它已經(jīng)擁有的鎖所同步著的代碼塊。額,說的啥意思?每個中文都認識,但是組合在一起,就不知道啥意思了。

我們來舉個生活中的例子:

在現(xiàn)實生活中,我們一般只需要帶有自己大門的鑰匙(當(dāng)然,如果是合租的朋友還需要帶著自己房間的鑰匙)。當(dāng)我們開了大門的鑰匙,進入房間后,我們在去廚房或者是去衛(wèi)生間的時候,不用在拿鑰匙開廚房或者衛(wèi)生間的門了吧。為啥呢?因為我們已經(jīng)已經(jīng)有大門的鎖的鑰匙并且已經(jīng)進入到了房間了。廚房和衛(wèi)生間已經(jīng)在大門鎖管理的范圍內(nèi)了。這種場景站在并發(fā)鎖的角度來看的話:一同一個線程函數(shù)獲得鎖之后(你拿著鑰匙打開了大門之后),內(nèi)層遞歸函數(shù)依然能夠獲取到該鎖對象的代碼(進入房間后,房間內(nèi)的廚房衛(wèi)生間可以隨便出入)。這樣是不是就好理解了?

底層實現(xiàn)原理主要是利用通過繼承AQS來實現(xiàn)的,也是利用通過對volatile state的CAS操作+CLH隊列來實現(xiàn);

CAS:Compare and Swap 比較并交換。CAS的思想很簡單:3個參數(shù),一個當(dāng)前內(nèi)存值V、預(yù)期值A(chǔ),即將更新的值B,當(dāng)前僅當(dāng)預(yù)期值A(chǔ)和內(nèi)存值V相等的時候,將內(nèi)存值V修改為B,否則什么都不做。該操作是一個原子操作被廣泛的用于java的底層實現(xiàn)中,在java中,CAS主要是有sun.misc.Unsafe這個類通過JNI調(diào)用CPU底層指令實現(xiàn);更多底層的思想參考狼哥的文章cas的底層原理:https://www.jianshu.com/p/fb6e91b013cc

CLH隊列:也叫同步隊列,是帶頭結(jié)點的雙向非循環(huán)列表,是AQS的主要實現(xiàn)原理(結(jié)構(gòu)如下圖所示)

ReentrantLock源碼解析是什么

最常用的方式:

int a = 12;
        //注意:通常情況下,這個會設(shè)置成一個類變量,比如說Segement中的段鎖與copyOnWriteArrayList中的全局鎖
        final ReentrantLock lock = new ReentrantLock();
        
        lock.lock();//獲取鎖
        try {
            a++;//業(yè)務(wù)邏輯
        } catch (Exception e) {
        }finally{
            lock.unlock();//釋放鎖
        }

1、對于ReentrantLock需要掌握以下幾點

  • ReentrantLock的創(chuàng)建(公平鎖/非公平鎖)

  • 上鎖:lock()

  • 解鎖:unlock()

首先說一下類結(jié)構(gòu):

  • ReentrantLock-->Lock

  • NonfairSync/FairSync-->Sync-->AbstractQueuedSynchronizer-->AbstractOwnableSynchronizer

  • NonfairSync/FairSync-->Sync是ReentrantLock的三個內(nèi)部類

  • Node是AbstractQueuedSynchronizer的內(nèi)部類

注意:上邊這四條線,對應(yīng)關(guān)系:"子類"-->"父類"

2、ReentrantLock的創(chuàng)建

  • 支持公平鎖(先進來的線程先執(zhí)行)

  • 支持非公平鎖(后進來的線程也可能先執(zhí)行)

非公平鎖與非公平鎖的創(chuàng)建

  • 非公平鎖:ReentrantLock()或ReentrantLock(false)

    final ReentrantLock lock = new ReentrantLock();


  • 公平鎖:ReentrantLock(true)

    final ReentrantLock lock = new ReentrantLock(true)


默認情況下使用非公平鎖。

源代碼如下:

ReentrantLock:

/** 同步器:內(nèi)部類Sync的一個引用 */
    private final Sync sync;

    /**
     * 創(chuàng)建一個非公平鎖
     */
    public ReentrantLock() {
        sync = new NonfairSync();
    }

    /**
     * 創(chuàng)建一個鎖
     * @param fair true-->公平鎖  false-->非公平鎖
     */
    public ReentrantLock(boolean fair) {
        sync = (fair)? new FairSync() : new NonfairSync();
    }

上述源代碼中出現(xiàn)了三個內(nèi)部類Sync/NonfairSync/FairSync,這里只列出類的定義,至于這三個類中的具體的方法會在后續(xù)的第一次引用的時候介紹。

Sync/NonfairSync/FairSync類定義:

/*
     * 該鎖同步控制的一個基類.下邊有兩個子類:非公平機制和公平機制.使用了AbstractQueuedSynchronizer類的
     */
    static abstract class Sync extends AbstractQueuedSynchronizer

    /**
     * 非公平鎖同步器
     */
    final static class NonfairSync extends Sync

    /**
     * 公平鎖同步器
     */
    final static class FairSync extends Sync

3、非公平鎖的lock()

具體使用方法:

lock.lock();

下面先介紹一下這個總體步驟的簡化版,然后會給出詳細的源代碼,并在源代碼的lock()方法部分給出詳細版的步驟。

簡化版的步驟:(非公平鎖的核心)

基于CAS嘗試將state(鎖數(shù)量)從0設(shè)置為1

A、如果設(shè)置成功,設(shè)置當(dāng)前線程為獨占鎖的線程;

B、如果設(shè)置失敗,還會再獲取一次鎖數(shù)量,

B1、如果鎖數(shù)量為0,再基于CAS嘗試將state(鎖數(shù)量)從0設(shè)置為1一次,如果設(shè)置成功,設(shè)置當(dāng)前線程為獨占鎖的線程;

B2、如果鎖數(shù)量不為0或者上邊的嘗試又失敗了,查看當(dāng)前線程是不是已經(jīng)是獨占鎖的線程了,如果是,則將當(dāng)前的鎖數(shù)量+1;如果不是,則將該線程封裝在一個Node內(nèi),并加入到等待隊列中去。等待被其前一個線程節(jié)點喚醒。

源代碼:(再介紹源代碼之前,心里有一個獲取鎖的步驟的總的一個印象,就是上邊這個"簡化版的步驟")

3.1、ReentrantLock:lock()

/**
     *獲取一個鎖
     *三種情況:
     *1、如果當(dāng)下這個鎖沒有被任何線程(包括當(dāng)前線程)持有,則立即獲取鎖,鎖數(shù)量==1,之后再執(zhí)行相應(yīng)的業(yè)務(wù)邏輯
     *2、如果當(dāng)前線程正在持有這個鎖,那么鎖數(shù)量+1,之后再執(zhí)行相應(yīng)的業(yè)務(wù)邏輯
     *3、如果當(dāng)下鎖被另一個線程所持有,則當(dāng)前線程處于休眠狀態(tài),直到獲得鎖之后,當(dāng)前線程被喚醒,鎖數(shù)量==1,再執(zhí)行相應(yīng)的業(yè)務(wù)邏輯
     */
    public void lock() {
        sync.lock();//調(diào)用NonfairSync(非公平鎖)或FairSync(公平鎖)的lock()方法
    }

3.2、NonfairSync:lock()

/**
         * 1)首先基于CAS將state(鎖數(shù)量)從0設(shè)置為1,如果設(shè)置成功,設(shè)置當(dāng)前線程為獨占鎖的線程;-->請求成功-->第一次插隊
         * 2)如果設(shè)置失敗(即當(dāng)前的鎖數(shù)量可能已經(jīng)為1了,即在嘗試的過程中,已經(jīng)被其他線程先一步占有了鎖),這個時候當(dāng)前線程執(zhí)行acquire(1)方法
         * 2.1)acquire(1)方法首先調(diào)用下邊的tryAcquire(1)方法,在該方法中,首先獲取鎖數(shù)量狀態(tài),
         * 2.1.1)如果為0(證明該獨占鎖已被釋放,當(dāng)下沒有線程在使用),這個時候我們繼續(xù)使用CAS將state(鎖數(shù)量)從0設(shè)置為1,如果設(shè)置成功,當(dāng)前線程獨占鎖;-->請求成功-->第二次插隊;當(dāng)然,如果設(shè)置不成功,直接返回false
         * 2.2.2)如果不為0,就去判斷當(dāng)前的線程是不是就是當(dāng)下獨占鎖的線程,如果是,就將當(dāng)前的鎖數(shù)量狀態(tài)值+1(這也就是可重入鎖的名稱的來源)-->請求成功
         * 
         * 下邊的流程一句話:請求失敗后,將當(dāng)前線程鏈入隊尾并掛起,之后等待被喚醒。
         * 
         * 2.2.3)如果最后在tryAcquire(1)方法中上述的執(zhí)行都沒成功,即請求沒有成功,則返回false,繼續(xù)執(zhí)行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)方法
         * 2.2)在上述方法中,首先會使用addWaiter(Node.EXCLUSIVE)將當(dāng)前線程封裝進Node節(jié)點node,然后將該節(jié)點加入等待隊列(先快速入隊,如果快速入隊不成功,其使用正常入隊方法無限循環(huán)一直到Node節(jié)點入隊為止)
         * 2.2.1)快速入隊:如果同步等待隊列存在尾節(jié)點,將使用CAS嘗試將尾節(jié)點設(shè)置為node,并將之前的尾節(jié)點插入到node之前
         * 2.2.2)正常入隊:如果同步等待隊列不存在尾節(jié)點或者上述CAS嘗試不成功的話,就執(zhí)行正常入隊(該方法是一個無限循環(huán)的過程,即直到入隊為止)-->第一次阻塞
         * 2.2.2.1)如果尾節(jié)點為空(初始化同步等待隊列),創(chuàng)建一個dummy節(jié)點,并將該節(jié)點通過CAS嘗試設(shè)置到頭節(jié)點上去,設(shè)置成功的話,將尾節(jié)點也指向該dummy節(jié)點(即頭節(jié)點和尾節(jié)點都指向該dummy節(jié)點)
         * 2.2.2.1)如果尾節(jié)點不為空,執(zhí)行與快速入隊相同的邏輯,即使用CAS嘗試將尾節(jié)點設(shè)置為node,并將之前的尾節(jié)點插入到node之前
         * 最后,如果順利入隊的話,就返回入隊的節(jié)點node,如果不順利的話,無限循環(huán)去執(zhí)行2.2)下邊的流程,直到入隊為止
         * 2.3)node節(jié)點入隊之后,就去執(zhí)行acquireQueued(final Node node, int arg)(這又是一個無限循環(huán)的過程,這里需要注意的是,無限循環(huán)等于阻塞,多個線程可以同時無限循環(huán)--每個線程都可以執(zhí)行自己的循環(huán),這樣才能使在后邊排隊的節(jié)點不斷前進)
         * 2.3.1)獲取node的前驅(qū)節(jié)點p,如果p是頭節(jié)點,就繼續(xù)使用tryAcquire(1)方法去嘗試請求成功,-->第三次插隊(當(dāng)然,這次插隊不一定不會使其獲得執(zhí)行權(quán),請看下邊一條),
         * 2.3.1.1)如果第一次請求就成功,不用中斷自己的線程,如果是之后的循環(huán)中將線程掛起之后又請求成功了,使用selfInterrupt()中斷自己
         * (注意p==head&&tryAcquire(1)成功是唯一跳出循環(huán)的方法,在這之前會一直阻塞在這里,直到其他線程在執(zhí)行的過程中,不斷的將p的前邊的節(jié)點減少,直到p成為了head且node請求成功了--即node被喚醒了,才退出循環(huán))
         * 2.3.1.2)如果p不是頭節(jié)點,或者tryAcquire(1)請求不成功,就去執(zhí)行shouldParkAfterFailedAcquire(Node pred, Node node)來檢測當(dāng)前節(jié)點是不是可以安全的被掛起,
         * 2.3.1.2.1)如果node的前驅(qū)節(jié)點pred的等待狀態(tài)是SIGNAL(即可以喚醒下一個節(jié)點的線程),則node節(jié)點的線程可以安全掛起,執(zhí)行2.3.1.3)
         * 2.3.1.2.2)如果node的前驅(qū)節(jié)點pred的等待狀態(tài)是CANCELLED,則pred的線程被取消了,我們會將pred之前的連續(xù)幾個被取消的前驅(qū)節(jié)點從隊列中剔除,返回false(即不能掛起),之后繼續(xù)執(zhí)行2.3)中上述的代碼
         * 2.3.1.2.3)如果node的前驅(qū)節(jié)點pred的等待狀態(tài)是除了上述兩種的其他狀態(tài),則使用CAS嘗試將前驅(qū)節(jié)點的等待狀態(tài)設(shè)為SIGNAL,并返回false(因為CAS可能會失敗,這里不管失敗與否,都返回false,下一次執(zhí)行該方法的之后,pred的等待狀態(tài)就是SIGNAL了),之后繼續(xù)執(zhí)行2.3)中上述的代碼
         * 2.3.1.3)如果可以安全掛起,就執(zhí)行parkAndCheckInterrupt()掛起當(dāng)前線程,之后,繼續(xù)執(zhí)行2.3)中之前的代碼
         * 最后,直到該節(jié)點的前驅(qū)節(jié)點p之前的所有節(jié)點都執(zhí)行完畢為止,我們的p成為了頭節(jié)點,并且tryAcquire(1)請求成功,跳出循環(huán),去執(zhí)行。
         * (在p變?yōu)轭^節(jié)點之前的整個過程中,我們發(fā)現(xiàn)這個過程是不會被中斷的)
         * 2.3.2)當(dāng)然在2.3.1)中產(chǎn)生了異常,我們就會執(zhí)行cancelAcquire(Node node)取消node的獲取鎖的意圖。
         */
        final void lock() {
            if (compareAndSetState(0, 1))//如果CAS嘗試成功
                setExclusiveOwnerThread(Thread.currentThread());//設(shè)置當(dāng)前線程為獨占鎖的線程
            else
                acquire(1);
        }

注意:在這個方法中,我列出了一個線程獲取鎖的詳細的過程,自己看注釋。

下面列出NonfairSync:lock()中調(diào)用的幾個方法與相關(guān)屬性。

3.2.1、AbstractQueuedSynchronizer:鎖數(shù)量state屬性+相關(guān)方法:

/**
     * 鎖數(shù)量
     */
    private volatile int state;

    /**
     * 獲取鎖數(shù)量
     */
    protected final int getState() {
        return state;
    }

    protected final void setState(int newState) {
        state = newState;
    }

注意:state是volatile型的

3.2.2、AbstractOwnableSynchronizer:屬性+setExclusiveOwnerThread(Thread t)

/**
     * 當(dāng)前擁有獨占鎖的線程
     */
    private transient Thread exclusiveOwnerThread;

    /**
     * 設(shè)置獨占鎖的線程為線程t
     */
    protected final void setExclusiveOwnerThread(Thread t) {
        exclusiveOwnerThread = t;
    }

3.2.3、AbstractQueuedSynchronizer:屬性+acquire(int arg)

/**
     * 獲取鎖的方法
     * @param arg
     */
    public final void acquire(int arg) {
        if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();//中斷自己
    }

在介紹上邊這個方法之前,先要說一下AbstractQueuedSynchronizer的一個內(nèi)部類Node的整體構(gòu)造,源代碼如下:

/*
     * 同步等待隊列(雙向鏈表)中的節(jié)點
     */
    static final class Node {
        /** 線程被取消了 */
        static final int CANCELLED = 1;
        /** 
         * 如果前驅(qū)節(jié)點的等待狀態(tài)是SIGNAL,表示當(dāng)前節(jié)點將來可以被喚醒,那么當(dāng)前節(jié)點就可以安全的掛起了 
         * 否則,當(dāng)前節(jié)點不能掛起 
         */
        static final int SIGNAL = -1;
        /**線程正在等待條件*/
        static final int CONDITION = -2;
        /**
         * waitStatus value to indicate the next acquireShared should
         * unconditionally propagate
         */
        static final int PROPAGATE = -3;
        /** Marker to indicate a node is waiting in shared mode */
        static final Node SHARED = new Node();
        /** 一個標(biāo)記:用于表明該節(jié)點正在獨占鎖模式下進行等待 */
        static final Node EXCLUSIVE = null;
        //值就是前四個int(CANCELLED/SIGNAL/CONDITION/PROPAGATE),再加一個0

        volatile int waitStatus;
        /**前驅(qū)節(jié)點*/
        volatile Node prev;

        /**后繼節(jié)點*/
        volatile Node next;

        /**節(jié)點中的線程*/
        volatile Thread thread;

        /**
         * Link to next node waiting on condition, or the special value SHARED.
         * Because condition queues are accessed only when holding in exclusive
         * mode, we just need a simple linked queue to hold nodes while they are
         * waiting on conditions. They are then transferred to the queue to
         * re-acquire. And because conditions can only be exclusive, we save a
         * field by using special value to indicate shared mode.
         */
        Node nextWaiter;

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

        /**
         * 返回該節(jié)點前一個節(jié)點
         */
        final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }

        Node() { // Used to establish initial head or SHARED marker
        }

        Node(Thread thread, Node mode) { // 用于addWaiter中
            this.nextWaiter = mode;
            this.thread = thread;
        }

        Node(Thread thread, int waitStatus) { // Used by Condition
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
    }

注意:這里我給出了Node類的完整版,其中部分屬性與方法是在共享鎖的模式下使用的,而我們這里的ReentrantLock是一個獨占鎖,只需關(guān)注其中的與獨占鎖相關(guān)的部分就好(具體有注釋)

3.3、AbstractQueuedSynchronizer:acquire(int arg)方法中使用到的兩個方法

3.3.1、NonfairSync:tryAcquire(int acquires)

/**
         * 試著請求成功
         */
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }

Syn:

/**
         * 非公平鎖中被tryAcquire調(diào)用
         */
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();//獲取當(dāng)前線程
            int c = getState();//獲取鎖數(shù)量
            if (c == 0) {//如果鎖數(shù)量為0,證明該獨占鎖已被釋放,當(dāng)下沒有線程在使用
                if (compareAndSetState(0, acquires)) {//繼續(xù)通過CAS將state由0變?yōu)?,注意這里傳入的acquires為1
                    setExclusiveOwnerThread(current);//將當(dāng)前線程設(shè)置為獨占鎖的線程
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {//查看當(dāng)前線程是不是就是獨占鎖的線程
                int nextc = c + acquires;//如果是,鎖狀態(tài)的數(shù)量為當(dāng)前的鎖數(shù)量+1
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);//設(shè)置當(dāng)前的鎖數(shù)量
                return true;
            }
            return false;
        }

注意:這個方法就完成了"簡化版的步驟"中的"A/B/B1"三步,如果上述的請求不能成功,就要執(zhí)行下邊的代碼了,

下邊的代碼,用一句話介紹:請求失敗后,將當(dāng)前線程鏈入隊尾并掛起,之后等待被喚醒。在你看下邊的代碼的時候心里默記著這句話。

3.3.2、AbstractQueuedSynchronizer:addWaiter(Node mode)

/**
     * 將Node節(jié)點加入等待隊列
     * 1)快速入隊,入隊成功的話,返回node
     * 2)入隊失敗的話,使用正常入隊
     * 注意:快速入隊與正常入隊相比,可以發(fā)現(xiàn),正常入隊僅僅比快速入隊多而一個判斷隊列是否為空且為空之后的過程
     * @return 返回當(dāng)前要插入的這個節(jié)點,注意不是前一個節(jié)點
     */
    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);//創(chuàng)建節(jié)點
        /*
         * 快速入隊
         */
        Node pred = tail;//將尾節(jié)點賦給pred
        if (pred != null) {//尾節(jié)點不為空
            node.prev = pred;//將尾節(jié)點作為創(chuàng)造出來的節(jié)點的前一個節(jié)點,即將node鏈接到為節(jié)點后
            /**
             * 基于CAS將node設(shè)置為尾節(jié)點,如果設(shè)置失敗,說明在當(dāng)前線程獲取尾節(jié)點到現(xiàn)在這段過程中已經(jīng)有其他線程將尾節(jié)點給替換過了
             * 注意:假設(shè)有鏈表node1-->node2-->pred(當(dāng)然是雙鏈表,這里畫成雙鏈表才合適),
             * 通過CAS將pred替換成了node節(jié)點,即當(dāng)下的鏈表為node1-->node2-->node,
             * 然后根據(jù)上邊的"node.prev = pred"與下邊的"pred.next = node"將pred插入到雙鏈表中去,組成最終的鏈表如下:
             * node1-->node2-->pred-->node
             * 這樣的話,實際上我們發(fā)現(xiàn)沒有指定node2.next=pred與pred.prev=node2,這是為什么呢?
             * 因為在之前這兩句就早就執(zhí)行好了,即node2.next和pred.prev這連個屬性之前就設(shè)置好了
             */
            if (compareAndSetTail(pred, node)) {
                pred.next = node;//將node放在尾節(jié)點上
                return node;
            }
        }
        enq(node);//正常入隊
        return node;
    }

AbstractQueuedSynchronizer:enq(final Node node)

/**
     * 正常入隊
     * @param node
     * @return 之前的尾節(jié)點
     */
    private Node enq(final Node node) {
        for (;;) {//無限循環(huán),一定要阻塞到入隊成功為止
            Node t = tail;//獲取尾節(jié)點
            if (t == null) { //如果尾節(jié)點為null,說明當(dāng)前等待隊列為空
                /*Node h = new Node(); // Dummy header
                h.next = node;
                node.prev = h;
                if (compareAndSetHead(h)) {//根據(jù)代碼實際上是:compareAndSetHead(null,h)
                    tail = node;
                    return h;
                }*/
                /*
                 * 注意:上邊注釋掉的這一段代碼是jdk1.6.45中的,在后來的版本中,這一段改成了如下這段
                 * 基于CAS將新節(jié)點(一個dummy節(jié)點)設(shè)置到頭上head去,如果發(fā)現(xiàn)內(nèi)存中的當(dāng)前值不是null,則說明,在這個過程中,已經(jīng)有其他線程設(shè)置過了。
                 * 當(dāng)成功的將這個dummy節(jié)點設(shè)置到head節(jié)點上去時,我們又將這個head節(jié)點設(shè)置給了tail節(jié)點,即head與tail都是當(dāng)前這個dummy節(jié)點,
                 * 之后有新節(jié)點入隊的話,就插入到該dummy之后
                 */
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {//這一塊兒的邏輯與快速入隊完全相同
                node.prev = t;
                if (compareAndSetTail(t, node)) {//嘗試將node節(jié)點設(shè)為尾節(jié)點
                    t.next = node;//將node節(jié)點設(shè)為尾節(jié)點
                    return t;
                }
            }
        }
    }

注意:這里就是一個完整的入隊方法,具體邏輯看注釋和ReentrantLock:lock()的注釋部分的相關(guān)部分。

3.3.3、AbstractQueuedSynchronizer:acquireQueued(final Node node, int arg)

final boolean acquireQueued(final Node node, int arg) {
        try {
            boolean interrupted = false;
            /*
             * 無限循環(huán)(一直阻塞),直到node的前驅(qū)節(jié)點p之前的所有節(jié)點都執(zhí)行完畢,p成為了head且node請求成功了
             */
            for (;;) {
                final Node p = node.predecessor();//獲取插入節(jié)點的前一個節(jié)點p
                /*
                 * 注意:
                 * 1、這個是跳出循環(huán)的唯一條件,除非拋異常
                 * 2、如果p == head && tryAcquire(arg)第一次循環(huán)就成功了,interrupted為false,不需要中斷自己
                 *         如果p == head && tryAcquire(arg)第一次以后的循環(huán)中如果執(zhí)行了掛起操作后才成功了,interrupted為true,就要中斷自己了
                 */
                if (p == head && tryAcquire(arg)) {
                    setHead(node);//當(dāng)前節(jié)點設(shè)置為頭節(jié)點
                    p.next = null; 
                    return interrupted;//跳出循環(huán)
                }
                if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                    interrupted = true;//被中斷了
            }
        } catch (RuntimeException ex) {
            cancelAcquire(node);
            throw ex;
        }
    }

AbstractQueuedSynchronizer:shouldParkAfterFailedAcquire(Node pred, Node node)

/**
     * 檢測當(dāng)前節(jié)點是否可以被安全的掛起(阻塞)
     * @param pred    當(dāng)前節(jié)點的前驅(qū)節(jié)點
     * @param node    當(dāng)前節(jié)點
     */
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;//獲取前驅(qū)節(jié)點(即當(dāng)前線程的前一個節(jié)點)的等待狀態(tài)
        if (ws == Node.SIGNAL)//如果前驅(qū)節(jié)點的等待狀態(tài)是SIGNAL,表示當(dāng)前節(jié)點將來可以被喚醒,那么當(dāng)前節(jié)點就可以安全的掛起了
            return true;
        /*
         * 1)當(dāng)ws>0(即CANCELLED==1),前驅(qū)節(jié)點的線程被取消了,我們會將該節(jié)點之前的連續(xù)幾個被取消的前驅(qū)節(jié)點從隊列中剔除,返回false(即不能掛起)
         * 2)如果ws<=0&&!=SIGNAL,將當(dāng)前節(jié)點的前驅(qū)節(jié)點的等待狀態(tài)設(shè)為SIGNAL
         */
        if (ws > 0) {
            do {
                /*
                 * node.prev = pred = pred.prev;
                 * 上邊這句代碼相當(dāng)于下邊這兩句
                 * pred = pred.prev;
                 * node.prev = pred;
                 */
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * 嘗試將當(dāng)前節(jié)點的前驅(qū)節(jié)點的等待狀態(tài)設(shè)為SIGNAL
             * 1/這為什么用CAS,現(xiàn)在已經(jīng)入隊成功了,前驅(qū)節(jié)點就是pred,除了node外應(yīng)該沒有別的線程在操作這個節(jié)點了,那為什么還要用CAS?而不直接賦值呢?
             * (解釋:因為pred可以自己將自己的狀態(tài)改為cancel,也就是pred的狀態(tài)可能同時會有兩條線程(pred和node)去操作)
             * 2/既然前驅(qū)節(jié)點已經(jīng)設(shè)為SIGNAL了,為什么最后還要返回false
             * (因為CAS可能會失敗,這里不管失敗與否,都返回false,下一次執(zhí)行該方法的之后,pred的等待狀態(tài)就是SIGNAL了)
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

AbstractQueuedSynchronizer:

private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);//掛起當(dāng)前的線程
        return Thread.interrupted();//如果當(dāng)前線程已經(jīng)被中斷了,返回true
    }

以上就是一個線程獲取非公平鎖的整個過程(lock())。

4、公平鎖的lock()

具體用法與非公平鎖一樣

如果掌握了非公平鎖的流程,那么掌握公平鎖的流程會非常簡單,只有兩點不同(最后會講)。

簡化版的步驟:(公平鎖的核心)

獲取一次鎖數(shù)量,

B1、如果鎖數(shù)量為0,如果當(dāng)前線程是等待隊列中的頭節(jié)點,基于CAS嘗試將state(鎖數(shù)量)從0設(shè)置為1一次,如果設(shè)置成功,設(shè)置當(dāng)前線程為獨占鎖的線程;

B2、如果鎖數(shù)量不為0或者當(dāng)前線程不是等待隊列中的頭節(jié)點或者上邊的嘗試又失敗了,查看當(dāng)前線程是不是已經(jīng)是獨占鎖的線程了,如果是,則將當(dāng)前的鎖數(shù)量+1;如果不是,則將該線程封裝在一個Node內(nèi),并加入到等待隊列中去。等待被其前一個線程節(jié)點喚醒。

源代碼:

4.1、ReentrantLock:lock()

/**
     *獲取一個鎖
     *三種情況:
     *1、如果當(dāng)下這個鎖沒有被任何線程(包括當(dāng)前線程)持有,則立即獲取鎖,鎖數(shù)量==1,之后被喚醒再執(zhí)行相應(yīng)的業(yè)務(wù)邏輯
     *2、如果當(dāng)前線程正在持有這個鎖,那么鎖數(shù)量+1,之后被喚醒再執(zhí)行相應(yīng)的業(yè)務(wù)邏輯
     *3、如果當(dāng)下鎖被另一個線程所持有,則當(dāng)前線程處于休眠狀態(tài),直到獲得鎖之后,當(dāng)前線程被喚醒,鎖數(shù)量==1,再執(zhí)行相應(yīng)的業(yè)務(wù)邏輯
     */
    public void lock() {
        sync.lock();//調(diào)用FairSync的lock()方法
    }

4.2、FairSync:lock()

final void lock() {
            acquire(1);
        }

4.3、AbstractQueuedSynchronizer:acquire(int arg)就是非公平鎖使用的那個方法

4.3.1、FairSync:tryAcquire(int acquires)

/**
         * 獲取公平鎖的方法
         * 1)獲取鎖數(shù)量c
         * 1.1)如果c==0,如果當(dāng)前線程是等待隊列中的頭節(jié)點,使用CAS將state(鎖數(shù)量)從0設(shè)置為1,如果設(shè)置成功,當(dāng)前線程獨占鎖-->請求成功
         * 1.2)如果c!=0,判斷當(dāng)前的線程是不是就是當(dāng)下獨占鎖的線程,如果是,就將當(dāng)前的鎖數(shù)量狀態(tài)值+1(這也就是可重入鎖的名稱的來源)-->請求成功
         * 最后,請求失敗后,將當(dāng)前線程鏈入隊尾并掛起,之后等待被喚醒。
         */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (isFirst(current) && compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

最后,如果請求失敗后,將當(dāng)前線程鏈入隊尾并掛起,之后等待被喚醒,下邊的代碼與非公平鎖一樣。

總結(jié):公平鎖與非公平鎖對比

  • FairSync:lock()少了插隊部分(即少了CAS嘗試將state從0設(shè)為1,進而獲得鎖的過程)

  • FairSync:tryAcquire(int acquires)多了需要判斷當(dāng)前線程是否在等待隊列首部的邏輯(實際上就是少了再次插隊的過程,但是CAS獲取還是有的)。

最后說一句,

  • ReentrantLock是基于AbstractQueuedSynchronizer實現(xiàn)的,AbstractQueuedSynchronizer可以實現(xiàn)獨占鎖也可以實現(xiàn)共享鎖,ReentrantLock只是使用了其中的獨占鎖模式

  • 這一塊兒代碼比較多,邏輯比較復(fù)雜,最好在閱讀的過程中,可以拿一根筆畫畫入隊等與數(shù)據(jù)結(jié)構(gòu)相關(guān)的圖

  • 一定要記住"簡化版的步驟",這是整個非公平鎖與公平鎖的核心

看完上述內(nèi)容,你們掌握 ReentrantLock源碼解析是什么的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI