源碼|并發(fā)一枝花之ReentrantLock與AQS(1):lock、unlock

顯示鎖ReentrantLock的內(nèi)部同步依賴于AQS(AbstractQueuedSynchronizer)伟阔,因此,分析ReentrantLock必然涉及AQS掰伸。

本文假設(shè)讀者已熟練掌握AQS的基本原理(參考AQS的基本原理)皱炉,通過分析ReentrantLock#lock()與ReentrantLock#unlock()的實(shí)現(xiàn)原理,用實(shí)例幫助讀者理解AQS的等待隊(duì)列模型狮鸭。

JDK版本:oracle java 1.8.0_102

接口聲明

public interface Lock {
    void lock();

    void lockInterruptibly() throws InterruptedException;

    boolean tryLock();

    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    void unlock();

    Condition newCondition();
}

ReentrantLock對標(biāo)內(nèi)置鎖合搅,實(shí)現(xiàn)了Lock接口。忽略Condition相關(guān)歧蕉,主要提供lock灾部、unlock兩種語義,和兩種語義的衍生品惯退。

實(shí)現(xiàn)原理

繼承AQS

本文中的“繼承”指“擴(kuò)展extend”赌髓。

AQS復(fù)習(xí)

AQS并提供了多個(gè)未實(shí)現(xiàn)的protected方法,留給作者覆寫以開發(fā)不同的同步器:

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
    ...
    protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }
    protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
    }
    protected int tryAcquireShared(int arg) {
        throw new UnsupportedOperationException();
    }
    protected boolean tryReleaseShared(int arg) {
        throw new UnsupportedOperationException();
    }
    protected boolean tryReleaseShared(int arg) {
        throw new UnsupportedOperationException();
    }
    ...
}

而其他非私有方法則使用final修飾催跪,禁止子類覆寫锁蠕。

繼承

ReentrantLock支持公平、非公平兩種策略懊蒸,并通過繼承AQS實(shí)現(xiàn)了對應(yīng)兩種策略的同步器NonfairSync與FairSync荣倾。ReentrantLock默認(rèn)使用非公平策略,即NonfairSync:

    public ReentrantLock() {
        sync = new NonfairSync();
    }
    ...
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        final void lock() {
            ...
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }
    abstract static class Sync extends AbstractQueuedSynchronizer {
        ...
        abstract void lock();

        final boolean nonfairTryAcquire(int acquires) {
            ...
        }

        protected final boolean tryRelease(int releases) {
            ...
        }

        final ConditionObject newCondition() {
            return new ConditionObject();
        }
        ...
    }
    ...

先不追究細(xì)節(jié)榛鼎。下面以默認(rèn)的非公平策略為例逃呼,講解lock和unlock的實(shí)現(xiàn)。

lock

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

非公平策略下者娱,sync指向一個(gè)NonfairSync實(shí)例抡笼。

    static final class NonfairSync extends Sync {
        ...
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
        ...
    }

ReentrantLock用state表示“所有者線程已經(jīng)重復(fù)獲取該鎖的次數(shù)”。當(dāng)state等于0時(shí)黄鳍,表示當(dāng)前沒有線程持有該鎖推姻,因此,將state CAS設(shè)置為1框沟,并記錄排他的所有者線程ownerThread(ownerThread只會(huì)在0->1及1->0兩次狀態(tài)轉(zhuǎn)換中修改)藏古;否則,state必然大于0忍燥,則嘗試再獲取一次鎖拧晕。ownerThread將在state大于0時(shí),用于判斷重入性梅垄。

  • 排他性:如果線程T1已經(jīng)持有鎖L厂捞,則不允許除T1外的任何線程T持有該鎖L
  • 重入性:如果線程T1已經(jīng)持有鎖L,則允許線程T1多次獲取鎖L,更確切的說靡馁,獲取一次后欲鹏,可多次進(jìn)入鎖。

二者結(jié)合臭墨,描述了ReentrantLock的一個(gè)性質(zhì):允許ownerThread重入赔嚎,不允許其他線程進(jìn)入或重入。

acquire

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
    ...
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }
    ...
}

改寫:

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
    ...
    public final void acquire(int arg) {
        if (tryAcquire(arg)) {
            return;
        }
        Node newNode = addWaiter(Node.EXCLUSIVE);
        boolean interrupted = acquireQueued(newNode, arg);
        if (interrupted) {
            selfInterrupt();
        }
    }
    ...
}

首先胧弛,通過tryAcquire()嘗試獲取鎖尤误。按照AQS的約定,tryAcquire()返回true表示獲取成功叶圃,可直接返回袄膏;否則獲取失敗掺冠。如果獲取失敗,則向等待隊(duì)列中添加一個(gè)獨(dú)占模式的節(jié)點(diǎn)码党,并通過acquireQueued()阻塞的等待該節(jié)點(diǎn)被調(diào)用(即當(dāng)前線程被喚醒)德崭。如果是因?yàn)楸恢袛喽鴨拘训模瑒t復(fù)現(xiàn)中斷信號(hào)揖盘。

tryAcquire

NonfairSync覆寫了AQS#tryAcquire():

    static final class NonfairSync extends Sync {
        ...
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
        ...
    }
    abstract static class Sync extends AbstractQueuedSynchronizer {
        ...
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            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;
        }
        ...
    }

12-17行重復(fù)了NonfairSync#lock()中state=0時(shí)的狀態(tài)轉(zhuǎn)換眉厨。18行進(jìn)行排他性判斷,如果當(dāng)前線程等于ownerThread兽狭,則直接返回false憾股。否則,19-22行進(jìn)行重入箕慧,state加1(acquires=1)服球,表示所有者線程重復(fù)獲取該鎖的次數(shù)增加1。

實(shí)際上颠焦,NonfairSync#lock()不需要特殊處理state=0時(shí)的狀態(tài)轉(zhuǎn)換斩熊。可通過NonfairSync#tryAcquire()伐庭、Sync#nonfairTryAcquire()完成粉渠。

為什么19-22行不需要同步

注意,如果18行判斷當(dāng)前線程等于ownerThread圾另,則根據(jù)程序順序規(guī)則霸株,19-22行不需要同步。因?yàn)?strong>同一線程中集乔,第二次調(diào)用NonfairSync#tryAcquire()時(shí)(會(huì)進(jìn)入19-22行)去件,第一次調(diào)用鎖寫入的state、ownerThread一定是可見的。

為什么要用state表示重入次數(shù)

如果沒有記錄重入次數(shù)箫攀,則第一次釋放鎖時(shí)肠牲,會(huì)一次性把ownerThread多次重入的鎖都釋放掉,而此時(shí)“鎖中的代碼”還沒有執(zhí)行完成靴跛,造成混亂缀雳。

addWaiter

如果tryAcquire()獲取失敗,則要通過AQS#addWaiter()向等待隊(duì)列中添加一個(gè)獨(dú)占模式的節(jié)點(diǎn)梢睛,并返回該節(jié)點(diǎn):

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
    ...
    private Node addWaiter(Node mode) {
        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;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }
    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;
                }
            }
        }
    }
    ...
}

AQS#enq()中重復(fù)了9-15行的邏輯肥印,直接看enq()。

如果尾指針為null绝葡,則頭指針也一定為null深碱,表示等待隊(duì)列未初始化,就CAS初始化隊(duì)列(常見于無阻塞隊(duì)列的設(shè)計(jì)中藏畅,如源碼|并發(fā)一枝花之BlockingQueue)敷硅,然后繼續(xù)循環(huán)。如果尾指針非null愉阎,則隊(duì)列已初始化绞蹦,就CAS嘗試在尾節(jié)點(diǎn)后插入新的節(jié)點(diǎn)node。

在插入過程中榜旦,會(huì)出現(xiàn)“node.prev指向舊的尾節(jié)點(diǎn)幽七,但舊的尾節(jié)點(diǎn).next為null未指向node(盡管,尾指針指向node)”的狀態(tài)溅呢,即“隊(duì)列在prev方向一致澡屡,next方向不一致”。記住該狀態(tài)咐旧,分析ReentrantLock#unlock()時(shí)會(huì)用到驶鹉。

最后,enq()返回舊的尾節(jié)點(diǎn)休偶。但外層的AQS#addWaiter()仍然返回新節(jié)點(diǎn)node梁厉。

隊(duì)列剛完成初始化時(shí),存在一個(gè)dummy node踏兜。插入節(jié)點(diǎn)時(shí)词顾,tail后移指向新節(jié)點(diǎn),head不變?nèi)匀恢赶騞ummy node碱妆。直到調(diào)用AQS#acquireQueued()時(shí)肉盹,head才會(huì)后移,消除了dummy node疹尾,后面分析上忍。

acquireQueued

插入新節(jié)點(diǎn)node后骤肛,通過AQS#acquireQueued()阻塞的等待該節(jié)點(diǎn)被調(diào)用(即當(dāng)前線程被喚醒):

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
    ...
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
    ...
}

該方法是lock過程的核心難點(diǎn),需要結(jié)合AQS#addWaiter()理解AQS內(nèi)部基于等待隊(duì)列的同步模型窍蓝。

AQS的核心是狀態(tài)依賴腋颠,可概括為兩條規(guī)則:

  • 當(dāng)狀態(tài)還沒有滿足的時(shí)候,節(jié)點(diǎn)會(huì)進(jìn)入等待隊(duì)列吓笙。
  • 特別的淑玫,獲取成功的節(jié)點(diǎn)成為隊(duì)列的頭結(jié)點(diǎn)。

首先面睛,AQS#addWaiter()會(huì)將新節(jié)點(diǎn)node加入隊(duì)尾(維護(hù)規(guī)則“當(dāng)狀態(tài)還沒有滿足的時(shí)候絮蒿,節(jié)點(diǎn)會(huì)進(jìn)入等待隊(duì)列”),然后叁鉴,AQS#acquireQueued()檢查node的前繼節(jié)點(diǎn)是否是頭節(jié)點(diǎn)土涝。如果是,則嘗試獲取鎖幌墓;如果不是但壮,或獲取所失敗,都會(huì)嘗試阻塞等待克锣。

如果11行獲取鎖成功茵肃,則更新頭節(jié)點(diǎn)(維護(hù)規(guī)則“獲取成功的節(jié)點(diǎn)成為隊(duì)列的頭結(jié)點(diǎn)”),修改failed標(biāo)志袭祟,并返回interrupted標(biāo)志。interrupted初始化為false捞附,可能在17-19行被修改巾乳。

初始化隊(duì)列后的第一次更新頭結(jié)點(diǎn),直接setHead消除了dummy node鸟召。消除之后胆绊,實(shí)際節(jié)點(diǎn)代替了dummy node的作用,但與dummy node不同的是欧募,該節(jié)點(diǎn)是持有鎖的压状。

如果11行判斷前繼節(jié)點(diǎn)不是頭節(jié)點(diǎn)或獲取鎖失敗,則進(jìn)入17-19行跟继。AQS.shouldParkAfterFailedAcquire()判斷是否需要阻塞等待种冬,如果需要,則通過AQS#parkAndCheckInterrupt()阻塞等待舔糖,直到被喚醒或被中斷娱两。

shouldParkAfterFailedAcquire

AQS.shouldParkAfterFailedAcquire()根據(jù)pred.waitStatus判斷新節(jié)點(diǎn)node是否應(yīng)該被阻塞:

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
    ...
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            return true;
        if (ws > 0) {
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
    ...
}

AQS#addWaiter()構(gòu)造新節(jié)點(diǎn)時(shí),pred.waitStatus使用了默認(rèn)值0金吗。此時(shí)十兢,進(jìn)入14-16行趣竣,CAS設(shè)置pred.waitStatus為SIGNAL==-1。最后返回false旱物。

回到AQS#acquireQueued()中后遥缕,由于AQS#parkAndCheckInterrupt()返回false,循環(huán)會(huì)繼續(xù)進(jìn)行宵呛。假設(shè)node的前繼節(jié)點(diǎn)pred仍然不是頭結(jié)點(diǎn)或鎖獲取失敗单匣,則會(huì)再次進(jìn)入AQS#parkAndCheckInterrupt()。上一輪循環(huán)中烤蜕,已經(jīng)將pred.waitStatus設(shè)置為SIGNAL==-1警绩,則這次會(huì)進(jìn)入7-8行,直接返回true嚣州,表示應(yīng)該阻塞归粉。

什么時(shí)候會(huì)遇到ws > 0的case呢?當(dāng)pred所維護(hù)的獲取請求被取消時(shí)橱鹏,pred.waitStatus會(huì)被設(shè)置為CANCELLED==1膜蠢,從而進(jìn)入9-14行。改寫:

if (ws > 0) {
    do {
        pred = pred.prev;
        node.prev = pred;
    } while (pred.waitStatus > 0);
    pred.next = node;
}

邏輯很簡單莉兰,循環(huán)移除所有被取消的前繼節(jié)點(diǎn)pred挑围,直到找到未被取消的pred。移除所有被取消的前繼節(jié)點(diǎn)后糖荒,直接返回false杉辙。

注意,在執(zhí)行6行之前捶朵,隊(duì)列處于“node.prev指向最新的前繼節(jié)點(diǎn)蜘矢,但pred.next指向已經(jīng)移除的后繼節(jié)點(diǎn)”的狀態(tài),即“隊(duì)列在prev方向一致综看,next方向不一致”品腹。記住該狀態(tài),分析ReentrantLock#unlock()時(shí)會(huì)用到红碑。

此處不需要檢查前繼節(jié)點(diǎn)是否為null舞吭。因?yàn)榈却?duì)列的頭結(jié)點(diǎn)要么是dummy node,滿足dummy.waitStatus==0析珊;要么是剛替換的real node羡鸥,滿足real.waitStatus==0;要么是后繼節(jié)點(diǎn)已經(jīng)阻塞的節(jié)點(diǎn)唾琼,滿足real.waitStatus==SIGNAL==-1兄春。則最晚遍歷到頭結(jié)點(diǎn)時(shí),一定會(huì)退出循環(huán)锡溯,不會(huì)出現(xiàn)pred為null的情況赶舆。

回到AQS#acquireQueued()后哑姚,重新檢查前繼節(jié)點(diǎn)是否為頭節(jié)點(diǎn),并作出相應(yīng)處理芜茵。

經(jīng)過多次循環(huán)執(zhí)行AQS.shouldParkAfterFailedAcquire()后叙量,等待隊(duì)列趨于穩(wěn)定。最終的穩(wěn)定狀態(tài)為:

  • 除了頭節(jié)點(diǎn)九串,剩余節(jié)點(diǎn)都會(huì)返回true绞佩,表示需要阻塞等待
  • 除了尾節(jié)點(diǎn)猪钮,剩余節(jié)點(diǎn)都滿足waitStatus==SIGNAL品山,表示釋放后需要喚醒后繼節(jié)點(diǎn)
parkAndCheckInterrupt
public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
...
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }
...
}

AQS#parkAndCheckInterrupt()借助LockSupport.park()實(shí)現(xiàn)阻塞等待烤低。最后調(diào)用Thread.interrupted()檢查是否被中斷肘交,并清除中斷狀態(tài),并返回中斷標(biāo)志扑馁。

如果是被中斷的涯呻,則需要在外層AQS#acquireQueued()中重新設(shè)置中斷標(biāo)志interrupted,并在下一次循環(huán)中返回腻要。然后在更外層的AQS#acquire()中調(diào)用AQS.selfInterrupt()重放中斷复罐。

為什么不能直接在AQS#parkAndCheckInterrupt()返回后中斷?因?yàn)榉祷刂修D(zhuǎn)標(biāo)志能提供更大的靈活性雄家,外界可以自行決定是即時(shí)重放效诅、稍后重放還是壓根不重放。Condition在得知AQS#acquireQueued()是被中斷的之后趟济,便沒有直接復(fù)現(xiàn)中斷填帽,而是根據(jù)REINTERRUPT配置決定是否重放。

cancelAcquire

最后咙好,如果在執(zhí)行AQS#acquire()的過程中拋出任何異常,則取消任務(wù):

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
    ...
    private void cancelAcquire(Node node) {
        ...
        node.waitStatus = Node.CANCELLED;
        ...
    }
    ...
}

因此褐荷,如果指考慮ReentrantLock#lock()方法的話勾效,那么被標(biāo)記為CACELLED狀態(tài)的節(jié)點(diǎn)一定在獲取鎖時(shí)拋出了異常,AQS.shouldParkAfterFailedAcquire()中清理了這部分CACELLED節(jié)點(diǎn)叛甫。

超時(shí)版ReentrantLock#tryLock()中层宫,還可以由于超時(shí)觸發(fā)取消。

lock小結(jié)

ReentrantLock#lock()收斂后其监,AQS內(nèi)部的等待隊(duì)列如圖:

image.png

unlock

ReentrantLock#unlcok()與ReentrantLock#lcok()是對偶的萌腿。

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

獲取鎖以單位1進(jìn)行,釋放鎖時(shí)也以單位1進(jìn)行抖苦。

release

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
    ...
    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
    ...
}

首先毁菱,通過tryRelease()嘗試釋放鎖米死。按照AQS的約定,tryRelease()返回true表示已完全釋放贮庞,可喚醒所有阻塞線程峦筒;否則沒有完全釋放,不需要喚醒窗慎。如果已完全釋放物喷,則只需要喚醒頭結(jié)點(diǎn)的后繼節(jié)點(diǎn),該節(jié)點(diǎn)的ownerThread必然與頭結(jié)點(diǎn)不同(如果相同遮斥,則之前l(fā)ock時(shí)能夠重入峦失,不需要排隊(duì));否則沒有完全釋放术吗,不需要喚醒任何節(jié)點(diǎn)尉辑。

對于獨(dú)占鎖,“完全釋放”表示ownerThread的所有重入操作均已結(jié)束藐翎。

解釋8行的判斷

如果h == null材蹬,則隊(duì)列還未初始化(回憶AQS#enq())。如果h.waitStatus == 0吝镣,則要么剛剛初始化隊(duì)列堤器,只有一個(gè)dummy node,沒有后繼節(jié)點(diǎn)(回憶AQS#enq())末贾;要么后繼節(jié)點(diǎn)還沒被阻塞闸溃,不需要喚醒(回憶等待隊(duì)列的穩(wěn)定狀態(tài))。

tryRelease

對照tryAcquire()分析tryRelease():

    abstract static class Sync extends AbstractQueuedSynchronizer {
        ...
        protected final boolean tryRelease(int releases) {
            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;
        }
        ...
    }

5-6行很重要拱撵。如果存在某線程持有鎖辉川,則可以檢查unlock是否被ownerThread觸發(fā);如果不存在線程持有鎖拴测,則ownerThread==null乓旗,可以檢查是否在未lock的情況下進(jìn)行unlock,或者重復(fù)執(zhí)行了unlock集索。

因此屿愚,使用ReentrantLock時(shí),try-finally要這么寫:

Lock lock = new ReentrantLock();
lock.lock();
try {
 // do sth
} finally {
 lock.unlcok();
}

確保在調(diào)用lock()成功之后务荆,才能調(diào)用unlock()妆距。

接下來,8行判斷是否將要進(jìn)行1->0的狀態(tài)轉(zhuǎn)換函匕,如果是娱据,則可以完全釋放鎖,將ownerThread置為null盅惜。然后設(shè)置state中剩。

最后忌穿,返回是否可完全釋放的標(biāo)志free。

可見性問題

為了抓住核心功能咽安,前面一直忽略了一個(gè)很重要的問題——可見性伴网。忽略可見性問題的話,閱讀源碼基本沒有影響妆棒,但自己實(shí)現(xiàn)同步器時(shí)將帶來噩夢澡腾。

以此處為例,是應(yīng)該先執(zhí)行8-11行糕珊,還是先執(zhí)行12行呢动分?或者是無所謂呢?

為保障可見性红选,必須先執(zhí)行8-11行澜公,再執(zhí)行12行。因?yàn)?strong>exclusiveOwnerThread的可見性要借助于(piggyback)于volatile變量state:

    ...
    private transient Thread exclusiveOwnerThread;
    ...
    private volatile int state;
    ...

配套的喇肋,也必須先讀state坟乾,再讀exclusiveOwnerThread:

    abstract static class Sync extends AbstractQueuedSynchronizer {
        ...
        final boolean nonfairTryAcquire(int acquires) {
            ...
            int c = getState(); // 先讀state
            if (c == 0) {
                ...
            }
            else if (current == getExclusiveOwnerThread()) {    // 再讀exclusiveOwnerThread
                ...
            }
            return false;
        }
        ...
    }

核心是三條Happens-Before規(guī)則:

  • 程序順序規(guī)則:如果程序中操作A在操作B之前,那么在線程中操作A將在操作B之前執(zhí)行蝶防。
  • 傳遞性:如果操作A在操作B之前執(zhí)行甚侣,并且操作B在操作C之前執(zhí)行,那么操作A必須在操作C之前執(zhí)行间学。
  • volatile變量規(guī)則:對volatile變量的寫入操作必須在對該變量的讀操作之前執(zhí)行殷费。

具體來說,“先寫exclusiveOwnerThread再寫state低葫;先讀state再讀exclusiveOwnerThread”的方案详羡,保證了“在讀state之后,發(fā)生在寫state之前的寫exclusiveOwnerThread操作發(fā)生在讀state之后的讀exclusiveOwnerThread操作一定是可見的”嘿悬。

程序順序規(guī)則实柠、傳遞性兩條基本規(guī)則,經(jīng)常與監(jiān)視器鎖規(guī)則善涨、volatile變量規(guī)則顯示的搭配主到,一定要掌握

相對的躯概,線程啟動(dòng)規(guī)則、線程結(jié)束規(guī)則畔师、中斷規(guī)則娶靡、終結(jié)器規(guī)則則通常被隱式的使用。

unparkSuccessor

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
    ...
    private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }
    ...
}

對lock過程的分析中看锉,我們得知姿锭,隊(duì)列中所有節(jié)點(diǎn)的waitStatus要么為0塔鳍,要么為SIGNAL==-1。當(dāng)node.waitStatus==SIGNAL時(shí)呻此,表示node的后繼節(jié)點(diǎn)s已被阻塞或正在被阻塞÷秩遥現(xiàn)在需要喚醒s,則7-8行將node.waitStatus置0焚鲜。

注意掌唾,node.waitStatus一定不為CANCELLED==1,因?yàn)槿绻鹟ock()方法沒有執(zhí)行成功忿磅,就無法通過unlock()方法調(diào)用AQS#unparkSuccessor()糯彬。

接下來,10-18行從尾節(jié)點(diǎn)向前遍歷葱她,找到node后最靠近node的未取消的節(jié)點(diǎn)撩扒,如果存在該節(jié)點(diǎn)s(s!=null),就喚醒s.thread以競爭鎖吨些。

一致性問題

為什么要從尾節(jié)點(diǎn)向前遍歷搓谆,而不能從node向后遍歷?這是因?yàn)楹朗珹QS中的等待隊(duì)列基于一個(gè)弱一致性雙向鏈表實(shí)現(xiàn)泉手,允許某些時(shí)刻下,隊(duì)列在prev方向一致但校,next方向不一致螃诅。

理想情況下,隊(duì)列每時(shí)每刻都處于一致的狀態(tài)(強(qiáng)一致性模型)状囱,從node向后遍歷找第一個(gè)未取消節(jié)點(diǎn)是更高效的做法术裸。然而,維護(hù)一致性通常需要犧牲部分性能亭枷,為了進(jìn)一步的提升性能袭艺,腦洞大開的神牛們想出了各種高性能的弱一致性模型。盡管模型允許了更多弱一致狀態(tài)叨粘,但所有弱一致狀態(tài)都在控制之下猾编,不會(huì)出現(xiàn)一致性問題。

回憶lock過程的分析升敲,有兩個(gè)地方出現(xiàn)了這個(gè)弱一致狀態(tài):

  • AQS#enq()插入新節(jié)點(diǎn)(包括AQS#addWaiter())的過程中答倡,舊的尾節(jié)點(diǎn)next為null未指向新節(jié)點(diǎn)。對應(yīng)條件s == null驴党。如圖:
image.png
  • AQS.shouldParkAfterFailedAcquire()移除CACELLED節(jié)點(diǎn)的過程中瘪撇,中間節(jié)點(diǎn)指向已被移除的CACELLED節(jié)點(diǎn)。對應(yīng)條件s.waitStatus > 0。如圖:
image.png

因此倔既,從node開始恕曲,沿著next方向向后遍歷是行不通的。只能從tail開始渤涌,沿著prev方向向前遍歷佩谣,直到找到未取消的節(jié)點(diǎn)(s != null),或遍歷完node的所有后繼子孫(s == null实蓬。

當(dāng)然茸俭,s == null也可能表示node恰好是尾節(jié)點(diǎn),該狀態(tài)是強(qiáng)一致的瞳秽,但仍然可以復(fù)用該段代碼瓣履。

unlock小結(jié)

ReentrantLock#unlock()收斂后,AQS內(nèi)部的等待隊(duì)列如圖:

image.png

總結(jié)

本文的目標(biāo)是借助對ReentrantLock#lock()與ReentrantLock#unlock()的分析练俐,理解AQS的等待隊(duì)列模型袖迎。

實(shí)際上,ReentrantLock還有一些重要的特性和API腺晾,如ReentrantLock#lockInterruptibly()燕锥、ReentrantLock#newCondition()。后面分先后分析這兩個(gè)API悯蝉,加深對AQS的理解归形。


本文鏈接:源碼|并發(fā)一枝花之ReentrantLock與AQS(1):lock、unlock
作者:猴子007
出處:https://monkeysayhi.github.io
本文基于 知識(shí)共享署名-相同方式共享 4.0 國際許可協(xié)議發(fā)布鼻由,歡迎轉(zhuǎn)載暇榴,演繹或用于商業(yè)目的,但是必須保留本文的署名及鏈接蕉世。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蔼紧,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子狠轻,更是在濱河造成了極大的恐慌奸例,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件向楼,死亡現(xiàn)場離奇詭異查吊,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)湖蜕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進(jìn)店門逻卖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人昭抒,你說我怎么就攤上這事箭阶⌒椴瑁” “怎么了?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵仇参,是天一觀的道長。 經(jīng)常有香客問我婆殿,道長诈乒,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任婆芦,我火速辦了婚禮怕磨,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘消约。我一直安慰自己肠鲫,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布或粮。 她就那樣靜靜地躺著导饲,像睡著了一般。 火紅的嫁衣襯著肌膚如雪氯材。 梳的紋絲不亂的頭發(fā)上渣锦,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天,我揣著相機(jī)與錄音氢哮,去河邊找鬼袋毙。 笑死,一個(gè)胖子當(dāng)著我的面吹牛冗尤,可吹牛的內(nèi)容都是我干的听盖。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼裂七,長吁一口氣:“原來是場噩夢啊……” “哼皆看!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起碍讯,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤悬蔽,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后捉兴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蝎困,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年倍啥,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了禾乘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,163評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡虽缕,死狀恐怖始藕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤伍派,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布江耀,位于F島的核電站,受9級(jí)特大地震影響诉植,放射性物質(zhì)發(fā)生泄漏祥国。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一晾腔、第九天 我趴在偏房一處隱蔽的房頂上張望舌稀。 院中可真熱鬧,春花似錦灼擂、人聲如沸壁查。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽睡腿。三九已至,卻和暖如春领斥,著一層夾襖步出監(jiān)牢的瞬間嫉到,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工月洛, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留何恶,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓嚼黔,卻偏偏與公主長得像细层,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子唬涧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評論 2 344

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