ReentrantLock源碼解析--加鎖過程

公平鎖的加鎖過程

首先ReentrantLock技能是公平鎖劲腿,又能是非公平鎖,這里先討論公平鎖的加鎖過程

public static void main(String[] args) {
    ReentrantLock reentrantLock = new ReentrantLock(true);
    reentrantLock.lock();
}

當我們使用帶參數(shù)的造器生成ReentrantLock時就珠,由于傳入是true所以會生成一個公平鎖的內(nèi)部類對象蛉威。

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

無論是公平鎖還是非公平鎖都繼承了ReentrantLock中Sync這個靜態(tài)內(nèi)部類對象仿荆,而這個對象又繼承了AbstractQueuedSynchronizer(以下簡稱AQS)臊泌,所以要分析ReenTrantLock的加鎖原理鲤桥,就要分析抽象類AbstractQueuedSynchronizer中的部分方法。現(xiàn)在我們從方法的調(diào)用開始逐行的分析里面的實現(xiàn)渠概。

當我們調(diào)用reentrantLock.lock時茶凳,其實會調(diào)用具體鎖的lock方法,當然這里指的是公平鎖播揪。這個方法是抽象類AQS需要具體類實現(xiàn)的方法贮喧。

public void lock() {
    sync.lock();
}

然后公平鎖的lock方法又調(diào)用acquire方法,并且穿了一個int類型的參數(shù)1猪狈,這個參數(shù)含義在下文分析

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

接著會調(diào)用AQS中的acquire方法箱沦。

/**
 * Acquires in exclusive mode, ignoring interrupts.  Implemented
 * by invoking at least once {@link #tryAcquire},
 * returning on success.  Otherwise the thread is queued, possibly
 * repeatedly blocking and unblocking, invoking {@link
 * #tryAcquire} until success.  This method can be used
 * to implement method {@link Lock#lock}.
 *
 * @param arg the acquire argument.  This value is conveyed to
 *        {@link #tryAcquire} but is otherwise uninterpreted and
 *        can represent anything you like.
 */
public final void acquire(int arg) {
    //(1)
    if (!tryAcquire(arg) &&
        //(2)
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        //(3)
        selfInterrupt();
}

我們來簡要分析一下類的注釋:

//以獨占方式獲取鎖,并忽略中斷雇庙。
//也就是說這個方法對線程的interrupt不做響應(yīng)谓形,如果線程在阻塞的時候因為interrupt被喚醒灶伊,則又會恢復(fù)阻塞狀態(tài),下文會進行分析它的實現(xiàn)套耕。
/*Acquires in exclusive mode, ignoring interrupts.*/
//該方法會至少調(diào)用一次tryAcquire方法谁帕,如果返回成功峡继,則方法結(jié)束不繼續(xù)執(zhí)行冯袍。否則將線程放入隊列(排隊)。
//tryAcquire方法會嘗試獲取鎖碾牌,如果獲取成功康愤,則當前線程繼續(xù)執(zhí)行。否則線程就進行排隊舶吗。
/* Implemented by invoking at least once {@link #tryAcquire}征冷,returning on success.Otherwise the thread is queued*/
//可能反復(fù)阻塞和解除阻塞(阻塞 - interrupt - 阻塞),直到成功(獲取到鎖)
/* possibly repeatedly blocking and unblocking, invoking {@link #tryAcquire} until success*/

代碼(1)調(diào)用tryAcquire(arg)方法獲取鎖誓琼,如果成功獲取到鎖检激,由于進行了非運算,則如果成功獲取到鎖腹侣,則直接返回叔收,不進行后續(xù)操作。

如果獲取鎖失敗傲隶,非運算后返回true,則會執(zhí)行acquireQueued(addWaiter(Node.EXCLUSIVE), arg))方法饺律,按照注釋所說,這里是將當前線程進行了排隊跺株。

最后如果兩個條件都返回true复濒,則會將自己打斷selfInterrupt(),在正常的加鎖過程中乒省,其實不會出現(xiàn)兩個都返回true的情況巧颈,這里是為reentrantLock.lockInterruptibly做的代碼復(fù)用。

小結(jié):對reentrantLock會根據(jù)構(gòu)造器生成的鎖類型來調(diào)用不同的lock方法袖扛。如果成功獲取鎖砸泛,則方法返回,線程繼續(xù)執(zhí)行攻锰,如果獲取鎖失敗晾嘶,則線程進行排隊。(但是線程如果獲取不到鎖娶吞,并不是直接排隊這么簡單)

tryAcquire方法分析垒迂。

現(xiàn)在我們先來分析一下(1)也就是tryAcauire方法,在AQS中該方法是個抽象方法妒蛇,需要具體的實現(xiàn)机断,這里的是公平鎖的tryAcquire方法楷拳。

/**
 * Fair version of tryAcquire.  Don't grant access unless
 * recursive call or no waiters or is first.
 */
protected final boolean tryAcquire(int acquires) {
  //(1.1)
    final Thread current = Thread.currentThread();
  //(1.2)
    int c = getState();
  //(1.3)
    if (c == 0) {
        //(1.4)
        if (!hasQueuedPredecessors() &&
          //(1.5)
            compareAndSetState(0, acquires)) {
          //(1.6)
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    //(1.7)
    else if (current == getExclusiveOwnerThread()) {
        //(1.8)
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        //(1.9)
        setState(nextc);
        return true;
    }
    return false;
}

現(xiàn)在我們逐行進行分析
(1.1)獲取當前線程
(1.2)getState()是AQS中的方法,返回的是獲取鎖的線程重入的次數(shù)(下文分析)吏奸。由于state是volatile聲明的欢揖,所以保證了可見性。

/**
 * Returns the current value of synchronization state.
 * This operation has memory semantics of a {@code volatile} read.
 * @return current state value
 */
protected final int getState() {
    return state;
}
/**
 * The synchronization state.
 */
private volatile int state;

(1.3)判斷線程狀態(tài)是否為0奋蔚,我們假設(shè)現(xiàn)在只有一個線程T1調(diào)用lock方法她混,由于這里的state是int類型的,默認值為0泊碑,所以c == 0返回true坤按,進入第一個if代碼塊。
(1.4)hasQueuedPredecessors()馒过,判斷隊列是否初始化臭脓,如果初始化,非運算后直接返回腹忽。如果隊列沒有初始化来累,則繼續(xù)執(zhí)行。

/**
 * Queries whether any threads have been waiting to acquire longer
 * than the current thread.
 *
 * <p>An invocation of this method is equivalent to (but may be
 * more efficient than):
 *  <pre> {@code
 * getFirstQueuedThread() != Thread.currentThread() &&
 * hasQueuedThreads()}</pre>
 *
 * <p>Note that because cancellations due to interrupts and
 * timeouts may occur at any time, a {@code true} return does not
 * guarantee that some other thread will acquire before the current
 * thread.  Likewise, it is possible for another thread to win a
 * race to enqueue after this method has returned {@code false},
 * due to the queue being empty.
 *
 * <p>This method is designed to be used by a fair synchronizer to
 * avoid <a href="AbstractQueuedSynchronizer#barging">barging</a>.
 * Such a synchronizer's {@link #tryAcquire} method should return
 * {@code false}, and its {@link #tryAcquireShared} method should
 * return a negative value, if this method returns {@code true}
 * (unless this is a reentrant acquire).  For example, the {@code
 * tryAcquire} method for a fair, reentrant, exclusive mode
 * synchronizer might look like this:
 *
 *  <pre> {@code
 * protected boolean tryAcquire(int arg) {
 *   if (isHeldExclusively()) {
 *     // A reentrant acquire; increment hold count
 *     return true;
 *   } else if (hasQueuedPredecessors()) {
 *     return false;
 *   } else {
 *     // try to acquire normally
 *   }
 * }}</pre>
 *
 * @return {@code true} if there is a queued thread preceding the
 *         current thread, and {@code false} if the current thread
 *         is at the head of the queue or the queue is empty
 * @since 1.7
 */
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());
}

簡要分析一下注釋

//查詢是否有線程比當前的線程等待了更多的時間窘奏。
//我們這里討論的是公平鎖嘹锁,所以遵循先進先出的原則。先來的線程會先獲取鎖蔼夜,這很好理解
/*Queries whether any threads have been waiting to acquire longer than the current thread. */
//調(diào)用該方法相當于執(zhí)行g(shù)etFirstQueuedThread() != Thread.currentThread() && hasQueuedThreads()
//也就是說這個方法就是判斷隊列的第一個線程是否是當前線程而且當前裝線程的隊列是否被初始化兼耀,但是這么寫的代碼執(zhí)行效率比較高    
/* * <p>An invocation of this method is equivalent to (but may be
     * more efficient than):
     *  <pre> {@code
     * getFirstQueuedThread() != Thread.currentThread() &&
     * hasQueuedThreads()}</pre>
*/
//由于中斷和超時在任何時候發(fā)生所以不能保證其他的線程先于當前線程獲得鎖,當隊列為空時求冷,也不能保證當前線程先于其他線程返回線程為空的這個結(jié)果瘤运。
//很好理解,CPU是按照時間片執(zhí)行的匠题,這段代碼有沒有同步機制拯坟,所以可能會出現(xiàn)兩個線程都返回該隊列為空的情況。但是后續(xù)代碼中的CAS操作可以保證只有一個拿到鎖韭山,并初始化隊列郁季。
/* * <p>Note that because cancellations due to interrupts and
 * timeouts may occur at any time, a {@code true} return does not
 * guarantee that some other thread will acquire before the current
 * thread.  Likewise, it is possible for another thread to win a
 * race to enqueue after this method has returned {@code false},
 * due to the queue being empty.
*/

簡要的說這個方法就是用來判斷隊列是否為空,如果隊列不為空钱磅,當前的線程是不是隊列頭部的第一個線程梦裂。
現(xiàn)在我們來分析代碼

        Node t = tail; // Read fields in reverse initialization order
        Node h = head;

這是一個簡單的賦值操作,來獲取隊列的頭尾節(jié)點盖淡,這個隊列是個雙向的隊列年柠。Node是隊列的節(jié)點,每個節(jié)點有一個前驅(qū)和后繼褪迟,以及存放著排隊的線程和排隊線程的狀態(tài)冗恨。

static final class Node{
    volatile Node prev;//前驅(qū)
    volatile Node next;//后繼
    volatile Thread thread;//線程
    volatile int waitStatus;//線程的狀態(tài)
}

在AQS中答憔,head和tail,并未被初始化,所以都為null

    private transient volatile Node head;
    private transient volatile Node tail;

現(xiàn)在我們假設(shè)只有一個線程T1進入了該方法掀抹,那么對于第一個判斷tail != head 不成立虐拓,該方法就直接返回false。說明隊列未初始化傲武。
現(xiàn)在我們返回到tryAcquire的(1.4)蓉驹,這時由于我們只有一個線程,則(1.4)返回了false谱轨,取反后條件成立戒幔,于是進入第二個判斷條件。

(1.5)接著執(zhí)行CAS操作土童,將acquires的值賦值給state,由之前的代碼可以看出這個值為1,如果賦值成功工坊,則說明成功獲取到鎖献汗。之前說過,由于hasQueuedPredecessors()判斷隊列是否為空的方法不是同步的王污,所以當執(zhí)行完代碼(1.4)后可能有多個線程都認為線程是空的罢吃,所以都會來改變state的狀態(tài),這時作者在(1.5)中使用了CAS操作昭齐,保證了有且只有一個線程能成功改變state的狀態(tài)尿招。

protected final boolean compareAndSetState(int expect, int update) {
    // See below for intrinsics setup to support this
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

這里調(diào)用了Unsafe類中的CAS操作。CAS操作的原理可自行去了解阱驾。
(1.6)將當前線程賦值給exclusiveOwnerThread 這個變量就谜,這個變量用來標記當前獲得鎖的線程。

protected final void setExclusiveOwnerThread(Thread thread) {
    exclusiveOwnerThread = thread;
}

最后方法返回true里覆。
小結(jié):公平鎖中的tryAcquire方法丧荐,會先獲取到當前線程的狀態(tài),如果當前狀態(tài)的state為0(0為初始狀態(tài))喧枷,就會判斷當前隊列是否被初始化虹统,或者當前隊列的第一個線程是否是當前線程。如果是隧甚,則嘗試改變state的狀態(tài)车荔,標記為1,也就是獲取到鎖被重入了一次戚扳,并記錄當前線程忧便,返回true。否則直接返回false咖城,并不嘗試改變state的狀態(tài)茬腿。

圖片.png

現(xiàn)在先讓我們返回AQS中的acquire方法呼奢。如果tryAcquire返回的結(jié)果為true。那么在取反后(1)為false線程直接返回切平,如果未獲取到鎖握础,那么進入(2),將線程加入隊列中悴品。

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

在流程圖中可以看到我們只分析了state == 0的情況,現(xiàn)在我們再來分析 state != 0 的情況禀综。

    //(1.7)
    else if (current == getExclusiveOwnerThread()) {
        //(1.8)
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        //(1.9)
        setState(nextc);
        return true;
    }

(1.7)如果當前的線程state不等與0,那么它就會判斷嘗試獲取鎖的線程是不是當前線程,getExclusiveOwnerThread()方法用來獲取當前已經(jīng)獲取到鎖的線程,在(1.6)中我們已經(jīng)保存了當前獲取到鎖的線程。

protected final Thread getExclusiveOwnerThread() {
    return exclusiveOwnerThread;
}

如果不是,則tryAcquire(arg)直接返回false苔严。如果是則更新state的狀態(tài)定枷。那么如何更新呢?由上文的傳參可知參數(shù)acquires的值為1届氢,c指代當前state的值欠窒,所以nextc可以理解為state++;
正常情況下不會出現(xiàn)nextc小于0的情況,這里不做討論退子。最后保存state的值岖妄,并返回true,標志獲取鎖成功寂祥。

這也就說明了荐虐,reentrantLock是個可重入的鎖,可重入次數(shù)理論上是int的最大值丸凭。

public static void main(String[] args) {
    ReentrantLock reentrantLock = new ReentrantLock(true);
    for(int i = 0; i <= Integer.MAX_VALUE;i++){
        reentrantLock.lock();
    }
}
//這段代碼會拋出 Maximum lock count exceeded 異常,也說明了重入次數(shù)是有限制的

到這里tryAcquire就分析完成了,我們能得出一個結(jié)論,如果線程是交替執(zhí)行的,那么reentrantLock.lock不會初始化隊列(因為tail和head都為null),也不會因為加鎖而影響代碼的性能(并沒有調(diào)用操作系統(tǒng)的方法)福扬。

acquireQueued(addWaiter(Node.EXCLUSIVE), arg))方法分析

假如獲取鎖失敗了,就會調(diào)用這個方法獲取隊列進行排隊,現(xiàn)在我們先來分析一下addWaiter。

/**
 * Creates and enqueues node for current thread and given mode.
 *
 * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
 * @return the new node
 */
private Node addWaiter(Node mode) {
    //(2.1)
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    //(2.2)
    Node pred = tail;
    //(2.3)
    if (pred != null) {
        //(2.4)
        node.prev = pred;
        //(2.5)
        if (compareAndSetTail(pred, node)) {
            //(2.6)
            pred.next = node;
            return node;
        }
    }
    //(2.7)
    enq(node);
    return node;
}

這段代碼乍看起來挺復(fù)雜,其實是雙向鏈表的新增插入操作惜犀。
在代碼(2.1)铛碑,我們新增了一個Node節(jié)點,并將當前線程和mode傳入向拆。在Node類中定義了一些靜態(tài)變量表標識這些mode,

    /** Marker to indicate a node is waiting in exclusive mode */
    static final Node EXCLUSIVE = null;

    /** waitStatus value to indicate thread has cancelled */
    static final int CANCELLED =  1;
    /** waitStatus value to indicate successor's thread needs unparking */
    static final int SIGNAL    = -1;
    /** waitStatus value to indicate thread is waiting on condition */
    static final int CONDITION = -2;
    /**
     * waitStatus value to indicate the next acquireShared should
     * unconditionally propagate
     */
    static final int PROPAGATE = -3;

這里傳入的是Node.EXCLUSIVE,也就是null.讀者不要將剛才的state與這個mode混淆,state在AQS中表示當前線程的重入次數(shù),初始為0亚茬。

現(xiàn)在假設(shè)由兩個線程先后獲取鎖,當T1線程來的時候浓恳,經(jīng)過tryAcquire發(fā)現(xiàn)隊列為空刹缝,于是CAS成功獲取鎖返回執(zhí)行,但是執(zhí)行了很長時間也沒有釋放鎖颈将,這時T2線程也來獲取鎖,由于state == 0 不成立,直接返回false梢夯。這時會先調(diào)用addWaiter來創(chuàng)建Node。

在初始的時候tail 和head都是空的,我們先創(chuàng)建了一個新節(jié)點T2,(2.2)將尾部賦值給節(jié)點prev,由于tail未初始化為空,所以prev也是空的,于是執(zhí)行(2.7)代碼晴圾。

這個方法會將新的節(jié)點插入隊列,如果隊列沒有初始化,就初始化隊列颂砸。其中傳入的參數(shù)node是T2節(jié)點。

/**
 * Inserts node into queue, initializing if necessary. See picture above.
 * @param node the node to insert
 * @return node's predecessor
 */
private Node enq(final Node node) {
    for (;;) {
        //(2.7.1)
        Node t = tail;
        //(2.7.2)
        if (t == null) { // Must initialize
            //(2.7.3)
            if (compareAndSetHead(new Node()))
                //(2.7.4)
                tail = head;
        } else {
            //(2.7.5)
            node.prev = t;
            //(2.7.6)
            if (compareAndSetTail(t, node)) {
                //(2.7.7)
                t.next = node;
                return t;
            }
        }
    }
}

在第一次for循環(huán)
tail是空的,所以t = null。
(2.7.1)將tail 賦值給臨時節(jié)點t人乓,剛才說過勤篮,tail是空的,滿足if條件色罚,于是初始化隊列碰缔。注意在(2.7.3)中同樣是個CAS操作,new 了一個空的新節(jié)點給head(不是我們用新線程創(chuàng)建的T2)戳护,然后將尾部指向頭部金抡。
在第二次for循環(huán)
先將尾節(jié)點賦值給t
(2.7.1)這時節(jié)點t就不是null了,是一個空的新節(jié)點,于是(2.7.2)條件不滿足,跳到(2.7.5)。
將空的節(jié)點t當作T2節(jié)點的前驅(qū)腌且,(2.7.6)然后嘗試將T2作為尾部梗肝。(2.7.7)最后空節(jié)點的后繼指向T2。簡單的說就是將剛創(chuàng)建的節(jié)點铺董,插入到隊列的尾部巫击。最后返回t,也就是空節(jié)點被返回了。

在這過程中如果CAS賦值失敗了,由于外層有個死循環(huán),所以一定會保證當前線程的節(jié)點成功的加入到隊列中柄粹。
這里為什么要先創(chuàng)建一個空的node呢喘鸟,我們暫時可以簡單看作為是指代獲取到鎖的那個線程,也就是T1。
我們再返回代碼(2.7)發(fā)現(xiàn)剛剛這個t的返回值并沒有被使用,返回的是我們剛創(chuàng)建的node,也就是隊列的尾節(jié)點驻右。

圖片.png

圖片.png

小節(jié):addWaiter方法會將新創(chuàng)建的node連接在隊列的尾部,如果隊列未初始化,則先將隊列初始化,需要特別注意的是,隊列的頭節(jié)點是一個空的node。

那如果是(2.3)pred != null的情況呢?我們再次假設(shè)現(xiàn)在又來了一個線程T3,T1獲得鎖正在運行,T2已經(jīng)初始化完成隊列,在隊尾排隊.這個隊列size =2,頭節(jié)點是空的node崎淳。新節(jié)點node為T3
(2.2)Node pred = tail 也就是pred的值為T2,pred != null進入(2.4)將T3的前驅(qū)指向T2在嘗試將T3設(shè)置為尾節(jié)點堪夭。也就是將T3加入尾部,如果還有T4,T5...也是一樣處理。

現(xiàn)在我們開始分析acquireQueued(final Node node, int arg) 方法

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            //(2.8)                
            final Node p = node.predecessor();
            //(2.9)                
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            //(2.10)                
            if (shouldParkAfterFailedAcquire(p, node) &&
                //(2.11)      
                parkAndCheckInterrupt())
                //(2.12)
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

這里也是一個死循環(huán),代碼(2.8)調(diào)用了predecessor()拣凹。這個方法就是返回當前節(jié)點的前驅(qū)森爽。
同樣我們假設(shè)還是只有兩個線程T1,T2,node指代的是T2線程。這里返回了T2的前驅(qū).所以p=空的node(上文分析過嚣镜,隊列的頭節(jié)點是個空的Node)爬迟。

final Node predecessor() throws NullPointerException {
    Node p = prev;
    if (p == null)
        throw new NullPointerException();
    else
        return p;
}

(2.9)p是不是頭節(jié)點,很顯然是的,接著又執(zhí)行了一次tryAcquire。啥意思呢菊匿,就是T2在這里自旋了一次付呕,嘗試獲取鎖,因為可能T1在T2創(chuàng)建隊列的時候就已經(jīng)釋放鎖了跌捆。那為什么p == head成功后才能執(zhí)行tryAcquire呢,因為只有排在第一位的線程才有資格嘗試獲取鎖,排在后面的線程就沒有必要執(zhí)行這個操作了(空節(jié)點指代獲得鎖的線程,并不是排隊線程,第一個排隊的是T2)徽职。

如果成功獲取到鎖,那么返回false,T2線程獲得鎖繼續(xù)執(zhí)行,如果自旋失敗,執(zhí)行(2.10)。這個方法用來更新node的狀態(tài),并判斷當前線程是否應(yīng)該進行阻塞掛起了佩厚。

/**
 * Checks and updates status for a node that failed to acquire.
 * Returns true if thread should block. This is the main signal
 * control in all acquire loops.  Requires that pred == node.prev.
 *
 * @param pred node's predecessor holding status
 * @param node the node
 * @return {@code true} if thread should block
 */
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    //(2.10.1)
    int ws = pred.waitStatus;
    //(2.10.2)        
    if (ws == Node.SIGNAL)
        /*
         * This node has already set status asking a release
         * to signal it, so it can safely park.
         */
        return true;
    //(2.10.3)        
    if (ws > 0) {
        /*
         * Predecessor was cancelled. Skip over predecessors and
         * indicate retry.
         */
        //(2.10.4)        
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        /*
         * waitStatus must be 0 or PROPAGATE.  Indicate that we
         * need a signal, but don't park yet.  Caller will need to
         * retry to make sure it cannot acquire before parking.
         */
        //(2.10.5)        
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

(2.10.1)ws用來獲取當前線程的等待狀態(tài)姆钉,我們之前在創(chuàng)建Node時,并沒有賦初始值,所以ws =0潮瓶。順便說一下Node.SIGNAL = -1,表示如果節(jié)點處于這個狀態(tài),那么這個節(jié)點的后繼節(jié)點將會或者已經(jīng)處于阻塞狀態(tài)(排隊掛起)陶冷。也就是說如果T1的ws = -1,那么T2將會被阻塞,或者已經(jīng)被阻塞。

(2.10.5)明顯ws = 0不符合條件進入else毯辅,將pred這個node狀態(tài)這是為-1,pred節(jié)點是代表T1的空節(jié)點埂伦,這也表示T2線程將要進入阻塞掛起狀態(tài)。最后返回false悉罕。這里的CAS為什么不進行自旋呢赤屋,因為當前線程CAS如果失敗了,會返回到acquireQueued方法的循環(huán),又一次執(zhí)行(2.10)方法改變狀態(tài)。

回到acquireQueue方法,(2.10)返回false于是第一次循環(huán)結(jié)束壁袄。

第二次循環(huán)p并沒有改變值,還是空節(jié)點类早,(2.9)p==head返回true,又進行了一次自旋嗜逻!涩僻,如果還是獲取不到鎖,進入(2.10.1)栈顷,這時ws已經(jīng)改變過一次了,值尾-1,直接返回true逆日。
所以第一個排隊的線程進行了兩次自旋。為什么不是只自旋一次萄凤,或者自旋3次或者更多呢室抽,一個因為將線程阻塞的開銷較大,如果線程能夠不阻塞排隊,那么最好不要,所以需要多給一次機會靡努,而且Node的狀態(tài)改變也需要兩次的循環(huán)坪圾。有其他看法的也歡迎討論。

(2.11.1)這里將線程阻塞了.于是整個加鎖過程結(jié)束,直到線程再次被喚醒,或者打斷(interrupt)惑朦。
如果線程被打斷兽泄,那么執(zhí)行(2.11.2)重置打斷狀態(tài),并返回true漾月,于是(2.12)將interrupt賦值為true,之后繼續(xù)循環(huán),并不做響應(yīng),再一次阻塞線程病梢。所以reentrantLock.lock不對中斷做出響應(yīng)。

    private final boolean parkAndCheckInterrupt() {
        //(2.11.1) 
        LockSupport.park(this);
        //(2.11.2)        
        return Thread.interrupted();
    }

公平鎖總結(jié):

  1. 在公平鎖中,如果線程獲取鎖時,會先判斷ReentrantLock中state的狀態(tài)(也是鎖被重入的次數(shù)),如果鎖未被持有,接著檢查隊列是否初始化,如果未初始化,嘗試就獲取鎖梁肿。如果鎖已經(jīng)被初始化蜓陌,或者隊列已經(jīng)被初始化,則讓當前線程排隊栈雳』つ危可以參考上文的流程圖。
  2. 在線程排隊之前,會先初始化隊列,隊列的第一個節(jié)點是空的,指代當前已經(jīng)獲取到鎖的線程(不是null)哥纫。
  3. 在線程加入隊列后,如果這個線程是第一個排隊的線程(不是空節(jié)點的線程霉旗,可以理解為上文的T2)痴奏,會進行兩次自旋嘗試獲取鎖。
  4. 如果線程在阻塞(park)時被打斷(interrupt)厌秒,并不會拋出異常读拆,或做出其他響應(yīng),而是重置打斷的狀態(tài)鸵闪,繼續(xù)阻塞檐晕。

非公平鎖加鎖過程

當我們使用如下方式創(chuàng)建的時候,reenTrantLock為非公平鎖

    ReentrantLock reentrantLock = new ReentrantLock();
    ReentrantLock reentrantLock1 = new ReentrantLock(false);

現(xiàn)在我們調(diào)用reenTrantLock.lock()方法。現(xiàn)在該方法會調(diào)用非公平鎖的lock蚌讼。

    public void lock() {
        sync.lock();
    }

我們可以先回顧一下公平鎖的加鎖方法,它在加鎖之間回顯判斷state的狀態(tài)是否為0,也就是當前鎖是否是可用的辟灰。如果為0,還要判斷隊列的狀態(tài)是否為空篡石,之后才有資格嘗試獲取鎖芥喇。而在非公平鎖的lock中,每個線程在加鎖的時候不管三七二十一都會先去嘗試獲取鎖,這就會出現(xiàn)正在排隊的線程鎖凰萨,被剛來的線程搶占的情況继控。

/**
 * Performs lock.  Try immediate barge, backing up to normal
 * acquire on failure.
 */
final void lock() {
    //(1)
    if (compareAndSetState(0, 1))
        //(2)
        setExclusiveOwnerThread(Thread.currentThread());
    else
        //(3)
        acquire(1);
}

(1)代碼CAS搶占鎖,如果搶占成功則記錄當前線程胖眷。如果執(zhí)行失敗執(zhí)行(3),這里同樣是AQS中的acquire方法武通。

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

接著就調(diào)用了非公平鎖中的tryAcquire方法。

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

接著調(diào)用了sync中的nonfairTryAcquire(int acquires)方法珊搀。在公平鎖中就直接將方法實現(xiàn)了冶忱,非公平鎖卻寫在了父類中。

    /**
     * Performs non-fair tryLock.  tryAcquire is implemented in
     * subclasses, but both need nonfair try for trylock method.
     */
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            //(3.1.1)
            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;
    }

非公平鎖語公平鎖的tryAcaquire幾乎相同境析,只不過在執(zhí)行(3.1.1)之前要先判斷隊列是否已經(jīng)初始化朗和,或者對頭線程是不是當前線程。其他代碼都一樣簿晓,這里就不在重復(fù)讀者可自行理解。

最后的入隊操作acquireQueued(addWaiter(Node.EXCLUSIVE), arg))和公平鎖是一樣的千埃。不再過多贅述憔儿。

非公平鎖總結(jié)

  1. 非公平鎖在加鎖之前會先搶占鎖。
  2. 非公平鎖在搶占鎖失敗后放可,與公平鎖一樣也會執(zhí)行tryAcquire方法谒臼,但是非公平鎖的tryAcquire在發(fā)現(xiàn)當前鎖可用之后,不會繼續(xù)判斷隊列是否初始化耀里,或者對頭是否是當前線程蜈缤,而是直接嘗試獲取鎖,如果失敗就加入隊列冯挎。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末底哥,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌趾徽,老刑警劉巖续滋,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異孵奶,居然都是意外死亡疲酌,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進店門了袁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來朗恳,“玉大人,你說我怎么就攤上這事载绿≈嘟耄” “怎么了?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵卢鹦,是天一觀的道長臀脏。 經(jīng)常有香客問我,道長冀自,這世上最難降的妖魔是什么揉稚? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮熬粗,結(jié)果婚禮上搀玖,老公的妹妹穿的比我還像新娘。我一直安慰自己驻呐,他們只是感情好灌诅,可當我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著含末,像睡著了一般猜拾。 火紅的嫁衣襯著肌膚如雪酝润。 梳的紋絲不亂的頭發(fā)上窟她,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天阿浓,我揣著相機與錄音砌函,去河邊找鬼借跪。 笑死舅巷,一個胖子當著我的面吹牛忍饰,可吹牛的內(nèi)容都是我干的卡儒。 我是一名探鬼主播蜜葱,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼全景,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了牵囤?” 一聲冷哼從身側(cè)響起爸黄,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤滞伟,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后馆纳,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體诗良,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年鲁驶,在試婚紗的時候發(fā)現(xiàn)自己被綠了鉴裹。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡钥弯,死狀恐怖径荔,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情脆霎,我是刑警寧澤总处,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站睛蛛,受9級特大地震影響鹦马,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜忆肾,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一荸频、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧客冈,春花似錦旭从、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至渠缕,卻和暖如春鸽素,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背亦鳞。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工付鹿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蚜迅。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像俊抵,于是被迫代替她去往敵國和親谁不。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,044評論 2 355