Java并發(fā)編程之鎖機(jī)制之Condition接口

book.jpg

前言

在前面的文章中剂陡,我曾提到過靡砌,整個Lock接口下實(shí)現(xiàn)的鎖機(jī)制中AQS(AbstractQueuedSynchronizer颤介,下文都稱之為AQS)Condition才是真正的實(shí)現(xiàn)者。也就說Condition在整個同步組件的基礎(chǔ)框架中也起著非常重要的作用菜秦,既然它如此重要與犀利,那么現(xiàn)在我們就一起去了解其內(nèi)部的實(shí)際原理與具體邏輯舶掖。

在閱讀該文章之前球昨,我由衷的建議先閱讀《Java并發(fā)編程之鎖機(jī)制之AQS》《Java并發(fā)編程之鎖機(jī)制之LockSupport工具》這兩篇文章。因?yàn)檎麄€Condtion的內(nèi)部機(jī)制與邏輯都離不開以上兩篇文章提到的知識點(diǎn)眨攘。

Condition接口方法介紹

在正式介紹Condtion之前主慰,我們可以先了解其中聲明的方法嚣州。具體方法聲明,如下表所示:

condition方法.png

從該表中共螺,我們可以看出其內(nèi)部定義了等待(以await開頭系列方法)通知(以singal開頭的系列方法)兩種類型的方法该肴,類似于Object對象的wait()notify()/NotifyAll()方法來對線程的阻塞與喚醒。

ConditionObject介紹

在實(shí)際使用中藐不,Condition接口實(shí)現(xiàn)類是AQS中的內(nèi)部類ConditionObject匀哄。在其內(nèi)部維護(hù)了一個FIFO(first in first out)的隊列(這里我們稱之為等待隊列,你也可以叫做阻塞隊列佳吞,看每個人的理解)拱雏,通過與AQS中的同步隊列配合使用,來控制獲取共享資源的線程底扳。

等待隊列

等待隊列是ConditionObjec中內(nèi)部的一個FIFO(first in first out)的隊列铸抑,在隊列中的每個節(jié)點(diǎn)都包含了一個線程引用,且該線程就是在ConditionObject對象上阻塞的線程衷模。需要注意的是鹊汛,在等待隊列中的節(jié)點(diǎn)是復(fù)用了AQSNode類的定義。換句話說阱冶,在AQS中維護(hù)的同步隊列與ConditionObjec中維護(hù)的等待隊列中的節(jié)點(diǎn)類型都是AQS.Node類型刁憋。(關(guān)于AbstractQueuedSynchronizer.Node類的介紹,大家可以參看《Java并發(fā)編程之鎖機(jī)制之AQS》文章中的描述)木蹬。

在ConditionObject類中也分別定義了firstWaiterlastWaiter兩個指針至耻,分別指向等待隊列中頭部與尾部。當(dāng)實(shí)際線程調(diào)用其以await開頭的系列方法后镊叁。會將該線程構(gòu)造為Node節(jié)點(diǎn)尘颓。添加等待隊列中的尾部。關(guān)于等待隊列的基本結(jié)構(gòu)如下圖所示:

condition內(nèi)部結(jié)構(gòu).png

對于等待隊列中節(jié)點(diǎn)添加的方式也很簡單晦譬,將上一尾節(jié)點(diǎn)的nextWaiter指向新添加的節(jié)點(diǎn)疤苹,同時使lastWaiter指向新添加的節(jié)點(diǎn)。

同步隊列與等待隊列的對應(yīng)關(guān)系

上文提到了整個Lock鎖機(jī)制需要AQS中的同步隊列ConditionObject的等待隊列配合使用敛腌,其對應(yīng)關(guān)系如下圖所示:

同步隊列與等待隊列的關(guān)系.png

在Lock鎖機(jī)制下卧土,可以擁有一個同步隊列和多個等待隊列,與我們傳統(tǒng)的Object監(jiān)視器模型上像樊,一個對象擁有一個同步隊列和等待隊列不同尤莺。lock中的鎖可以伴有多個條件。

Condition的基本使用

為了大家能夠更好的理解同步隊列與等待隊列的關(guān)系生棍。下面通過一個有界隊列BoundedBuffer來了解Condition的使用方式颤霎,該類是一個特殊的隊列,當(dāng)隊列為空時,隊列的獲取操作將會阻塞當(dāng)前"拿"線程捷绑,直到隊列中有新增的元素,當(dāng)隊列已滿時氢妈,隊列的放入操作將會阻塞"放入"線程粹污,直到隊列中出現(xiàn)空位。具體代碼如下所示:

class BoundedBuffer {

    final Lock lock = new ReentrantLock();
    final Condition notFull = lock.newCondition();
    final Condition notEmpty = lock.newCondition();

    final Object[] items = new Object[100];
    
    //依次為首量,放入的角標(biāo)壮吩、拿的角標(biāo)、數(shù)組中放入的對象總數(shù)
    int putptr, takeptr, count;

    /**
     * 添加一個元素
     * (1)如果當(dāng)前數(shù)組已滿加缘,則把當(dāng)前"放入"線程鸭叙,加入到"放入"等待隊列中,并阻塞當(dāng)前線程
     * (2)如果當(dāng)前數(shù)組未滿拣宏,則將x元素放入數(shù)組中沈贝,喚醒"拿"線程中的等待線程。
     */
    public void put(Object x) throws InterruptedException {
        lock.lock();
        try {
            while (count == items.length)//如果已滿勋乾,則阻塞當(dāng)前"放入"線程
                notFull.await();
            items[putptr] = x;
            if (++putptr == items.length) putptr = 0;
            ++count;
            notEmpty.signal();//喚醒"拿"線程
        } finally {
            lock.unlock();
        }
    }

    /**
     * 拿一個元素
     * (1)如果當(dāng)前數(shù)組已空宋下,則把當(dāng)前"拿"線程,加入到"拿"等待隊列中辑莫,并阻塞當(dāng)前線程
     * (2)如果當(dāng)前數(shù)組不為空学歧,則把喚醒"放入"等待隊列中的線程。
     */
    public Object take() throws InterruptedException {
        lock.lock();
        try {
            while (count == 0)//如果為空各吨,則阻塞當(dāng)前"拿"線程
                notEmpty.await();
            Object x = items[takeptr];
            if (++takeptr == items.length) takeptr = 0;
            --count;
            notFull.signal();//喚醒"放入"線程
            return x;
        } finally {
            lock.unlock();
        }
    }
}

從代碼中我們可以看出枝笨,在該類中我們創(chuàng)建了兩個等待隊列notFullnotEmpty。這兩個等待隊列的作用分別是揭蜒,當(dāng)請數(shù)組已滿時横浑,notFull用于存儲阻塞的"放入"線程,notEmpty用于存儲阻塞的"拿"線程忌锯。需要注意的是獲取一個Condition必須通過Lock的newCondition()方法伪嫁。關(guān)于ReentrantLock,在后續(xù)的文章中偶垮,我們會進(jìn)行介紹张咳。

阻塞實(shí)現(xiàn) await()

在了解了ConditionObject的內(nèi)部基本結(jié)構(gòu)和與AQS中內(nèi)部的同步隊列的對應(yīng)關(guān)系后,現(xiàn)在我們來看看其阻塞實(shí)現(xiàn)似舵。調(diào)用ConditionObject的await()方法(或者以await開頭的方法)脚猾,會使當(dāng)前線程進(jìn)入等待隊列,并釋放同步狀態(tài)砚哗,需要注意的是當(dāng)該方法返回時龙助,當(dāng)前線程一定獲取了同步狀態(tài)(具體原因是當(dāng)通過signal()等系列方法,線程才會從await()方法返回,而喚醒該線程后會加入同步隊列)提鸟。這里我們以awati()方法為例军援,具體代碼如下所示:

  public final void await() throws InterruptedException {
            //如果當(dāng)前線程已經(jīng)中斷,直接拋出異常  
            if (Thread.interrupted())
                throw new InterruptedException();
            //(1)將當(dāng)前線程加入等待隊列
            Node node = addConditionWaiter();
            //(2)釋放同步狀態(tài)(也就是釋放鎖)称勋,同時將線程節(jié)點(diǎn)從同步隊列中移除胸哥,并喚醒同步隊列中的下一節(jié)點(diǎn)
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            //(3)判斷當(dāng)前線程節(jié)點(diǎn)是否還在同步隊列中,如果不在則阻塞線程
            while (!isOnSyncQueue(node)) {
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            //(4)當(dāng)線程被喚醒后赡鲜,重新在同步隊列中與其他線程競爭獲取同步狀態(tài)
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

從代碼整體來看空厌,整個方法分為以下四個步驟:

  • (1)通過 addConditionWaiter()方法將線程節(jié)點(diǎn)加入到等待隊列中。
  • (2)通過fullyRelease(Node node)方法釋放同步狀態(tài)(也就是釋放鎖)银酬,同時將線程節(jié)點(diǎn)從同步隊列中移除嘲更,并喚醒同步隊列中的下一節(jié)點(diǎn)
  • (3)通過isOnSyncQueue(Node node)方法判斷當(dāng)前線程節(jié)點(diǎn)是否在同步隊列中揩瞪,如果不在赋朦,則通過LockSupport.park(this);阻塞當(dāng)前線程。
  • (4)當(dāng)線程被喚醒后李破,調(diào)用acquireQueued(node, savedState)方法北发,重新在同步隊列中與其他線程競爭獲取同步狀態(tài)

因?yàn)槊總€步驟涉及到的邏輯都稍微有一點(diǎn)復(fù)雜,這里為了方便大家理解喷屋,分別對以上四個步驟涉及到的方法分別進(jìn)行介紹琳拨。

addConditionWaiter()方法

該方法主要將同步隊列中的需要阻塞的線程節(jié)點(diǎn)加入到等待隊列中,關(guān)于addConditionWaiter()方法具體代碼如下所示:

    private Node addConditionWaiter() {
            Node t = lastWaiter;
            // (1)如果當(dāng)前尾節(jié)點(diǎn)中中對應(yīng)的線程已經(jīng)中斷屯曹,
            //則移除等待隊列中所有的已經(jīng)中斷或已經(jīng)釋放同步狀態(tài)的線程節(jié)點(diǎn)
            if (t != null && t.waitStatus != Node.CONDITION) {
                unlinkCancelledWaiters();
                t = lastWaiter;
            }
            //(2)構(gòu)建等待隊列中的節(jié)點(diǎn)
            Node node = new Node(Node.CONDITION);
            
            //(3)將該線程節(jié)點(diǎn)添加到隊列中狱庇,同時構(gòu)建firstWaiter與lastWaiter的指向
            if (t == null)
                firstWaiter = node;
            else
                t.nextWaiter = node;
            lastWaiter = node;
            return node;
        }

該方法的邏輯也比較簡單,分為以下三個步驟:

  • (1)獲取等待隊列中的尾節(jié)點(diǎn)恶耽,如果當(dāng)前尾節(jié)點(diǎn)已經(jīng)中斷密任,那么則通過unlinkCancelledWaiters()方法移除等待隊列中所有的已經(jīng)中斷已經(jīng)釋放同步狀態(tài)(也就是釋放鎖)的線程節(jié)點(diǎn)
  • (2)構(gòu)建等待隊列中的節(jié)點(diǎn),注意偷俭,是通過New的形式浪讳,那么就說明與同步隊列中的線程節(jié)點(diǎn)不是同一個。(對Node狀態(tài)枚舉不清楚的小伙伴涌萤,可以參看Java并發(fā)編程之鎖機(jī)制之AQS文章下的Node狀態(tài)枚舉介紹)淹遵。
  • (3)將該線程節(jié)點(diǎn)添加到等待隊列中去,同時構(gòu)建firstWaiter與lastWaiter的指向负溪,可以看出等待隊列總是以FIFO(first in first out )的形式添加線程節(jié)點(diǎn)透揣。
unlinkCancelledWaiters()方法

因?yàn)樵?code>addConditionWaiter()方法的步驟(1)中,調(diào)用了unlinkCancelledWaiters移除了所有的已經(jīng)中斷的線程節(jié)點(diǎn)川抡,那我們看一個該方法的實(shí)現(xiàn)辐真。如下所示:

  private void unlinkCancelledWaiters() {
            //獲取等待隊列中的頭節(jié)點(diǎn)
            Node t = firstWaiter;
            Node trail = null;
            //遍歷等待隊列,將已經(jīng)中斷的線程節(jié)點(diǎn)從等待隊列中移除。
            while (t != null) {
                Node next = t.nextWaiter;
                if (t.waitStatus != Node.CONDITION) {
                    t.nextWaiter = null;
                    if (trail == null)
                        firstWaiter = next;
                    else
                        trail.nextWaiter = next;
                    if (next == null)//重新定義lastWaiter的指向
                        lastWaiter = trail;
                }
                else
                    trail = t;
                t = next;
            }
        }

該方法具體流程如下圖所示:


condition.png

fullyRelease(Node node)

在將阻塞線程加入到等待隊列后侍咱,會將該線程節(jié)點(diǎn)從同步隊列中移除耐床,釋放同步狀態(tài)(也就是釋放鎖),并喚醒同步隊列中的下一節(jié)點(diǎn)楔脯。具體代碼如下所示:

  final int fullyRelease(Node node) {
        try {
            int savedState = getState();
            if (release(savedState))
                return savedState;
            throw new IllegalMonitorStateException();
        } catch (Throwable t) {
            node.waitStatus = Node.CANCELLED;
            throw t;
        }
    }

release(int arg)方法會釋放當(dāng)前線程的同步狀態(tài)咙咽, 并喚醒同步隊列中的下一線程節(jié)點(diǎn),使其嘗試獲取同步狀態(tài)淤年,因?yàn)樵摲椒ㄒ呀?jīng)在Java并發(fā)編程之鎖機(jī)制之AQS文章下的unparkSuccessorNode node)方法的下分析過了,所以這里就不再進(jìn)行分析了蜡豹。希望大家參考上面提到的文章進(jìn)行理解麸粮。

isOnSyncQueue(Node node)

該方法主要用于判斷當(dāng)前線程節(jié)點(diǎn)是否在同步隊列中。具體代碼如下所示:

  final boolean isOnSyncQueue(Node node) {
        //判斷當(dāng)前節(jié)點(diǎn) waitStatus ==Node.CONDITION或者當(dāng)前節(jié)點(diǎn)上一節(jié)點(diǎn)為空,則不在同步隊列中
        if (node.waitStatus == Node.CONDITION || node.prev == null)
            return false;
        //如果當(dāng)前節(jié)點(diǎn)擁有下一個節(jié)點(diǎn)镜廉,則在同步隊列中弄诲。
        if (node.next != null) // If has successor, it must be on queue
            return true;
        //如果以上條件都不滿足,則遍歷同步隊列娇唯。檢查是否在同步隊列中齐遵。
        return findNodeFromTail(node);
    }

如果你還記得AQS中的同步隊列,那么你應(yīng)該知道同步隊列中的Node節(jié)點(diǎn)才會使用其內(nèi)部的prenext字段塔插,那么在同步隊列中因?yàn)橹皇褂昧?code>nextWaiter字段梗摇,所以我們就能很簡單的通過這兩個字段是否為==null,來判斷是否在同步隊列中想许。當(dāng)然也有可能有一種特殊情況伶授。有可能需要阻塞的線程節(jié)點(diǎn)還沒有加入到同步隊列中,那么這個時候我們需要遍歷同步隊列來判斷是該線程節(jié)點(diǎn)是否已存在流纹。具體代碼如下所示:

 private boolean findNodeFromTail(Node node) {
        for (Node p = tail;;) {
            if (p == node)
                return true;
            if (p == null)
                return false;
            p = p.prev;
        }
    }

這里之所以使用同步隊列tail(尾節(jié)點(diǎn))來遍歷糜烹,如果node.netx!=null,那么就說明當(dāng)前線程已經(jīng)在同步隊列中漱凝。那么我們需要處理的情況肯定是針對node.next==null的情況疮蹦。所以需要從尾節(jié)點(diǎn)開始遍歷。

acquireQueued(final Node node, int arg)

當(dāng)線程被喚醒后(具體原因是當(dāng)通過signal()等系列方法茸炒,線程才會從await()方法返回)會調(diào)用該方法將該線程節(jié)點(diǎn)加入到同步隊列中愕乎。該方法我在《Java并發(fā)編程之鎖機(jī)制之AQS》中具體描述過了。這里就不在進(jìn)行過多的解析壁公。

阻塞流程

在理解了整個阻塞的流程后妆毕,現(xiàn)在我們來歸納總結(jié)一下,整個阻塞的流程贮尖。具體流程如下圖所示:


阻塞流程.png
  • (1)將該線程節(jié)點(diǎn)從同步隊列中移除笛粘,并釋放其同步狀態(tài)。
  • (2)構(gòu)造新的阻塞節(jié)點(diǎn),加入到等待隊列中薪前。

喚醒實(shí)現(xiàn) signal()

當(dāng)需要喚醒線程時润努,會調(diào)用ConditionObject中的singal開頭的系列方法,該系列方法會喚醒等待隊列中的首個線程節(jié)點(diǎn)示括,在喚醒該節(jié)點(diǎn)之前铺浇,會先講該節(jié)點(diǎn)移動到同步隊列中。這里我們以singal()方法為例進(jìn)行講解垛膝,具體代碼如下:

  public final void signal() {
            //(1)判斷當(dāng)前線程是否獲取到了同步狀態(tài)(也就是鎖)
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            //(2)獲取等待隊列中的首節(jié)點(diǎn)鳍侣,然后將其移動到同步隊列,然后再喚醒該線程節(jié)點(diǎn)
            if (first != null)
                doSignal(first);
        }

該方法主要邏輯分為以下兩個步驟:

  • (1)通過isHeldExclusively()方法吼拥,判斷當(dāng)前線程是否獲取到了同步狀態(tài)(也就是鎖)倚聚。
  • (2)通過doSignal(Node first)方法,獲取等待隊列中的首節(jié)點(diǎn)凿可,然后將其移動到同步隊列惑折,然后再喚醒該線程節(jié)點(diǎn)。

下面我們會分別對上面涉及到的兩個方法進(jìn)行描述枯跑。

isHeldExclusively()方法

isHeldExclusively()方法是AQS中的方法惨驶,默認(rèn)交給其子類實(shí)現(xiàn),主要用于判斷當(dāng)前調(diào)用singal()方法的線程敛助,是否在同步隊列中粗卜,且已經(jīng)獲取了同步狀態(tài)。具體代碼如下所示:

    protected boolean isHeldExclusively() {
        throw new UnsupportedOperationException();
    }

doSignal(Node first)方法

那我們繼續(xù)跟蹤doSignal(Node first)方法纳击,具體方法如下:

     private void doSignal(Node first) {
            do {
                //(1)將等待隊列中的首節(jié)點(diǎn)從等待隊列中移除休建,并重新制定firstWaiter的指向
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                first.nextWaiter = null;
            } while (!transferForSignal(first) &&
            //(2)將等待隊列中的首節(jié)點(diǎn),加入同步隊列中评疗,并重新喚醒該節(jié)點(diǎn)
                     (first = firstWaiter) != null);
        }

該方法也很簡單测砂,分為兩個步驟:

  • (1)將等待隊列中的首節(jié)點(diǎn)從等待隊列中移除,并設(shè)置firstWaiter的指向?yàn)槭坠?jié)點(diǎn)的下一個節(jié)點(diǎn)百匆。 為了方便大家理解該步驟所描述的邏輯砌些,這里畫了具體的圖,具體情況如下圖所示:


    移除首節(jié)點(diǎn).png
  • (2)通過 transferForSignal(Node node)方法加匈,將等待隊列中的首節(jié)點(diǎn)存璃,加入到同步隊列中去,然后重新喚醒該線程節(jié)點(diǎn)雕拼。
transferForSignal(Node node)方法

因?yàn)椴襟E(2)中transferForSignal(Node node)方法較為復(fù)雜纵东,所以會對該方法進(jìn)行詳細(xì)的講解。具體代碼如下所示:

    final boolean transferForSignal(Node node) {
       
        //(1)將該線程節(jié)點(diǎn)的狀態(tài)設(shè)置為初始狀態(tài)啥寇,如果失敗則表示當(dāng)前線程已經(jīng)中斷了
        if (!node.compareAndSetWaitStatus(Node.CONDITION, 0))
            return false;
        //(2)將該節(jié)點(diǎn)放入同步隊列中偎球,
        Node p = enq(node);
        int ws = p.waitStatus;
        //(3)獲取當(dāng)前節(jié)點(diǎn)的狀態(tài)并判斷洒扎,嘗試將該線程節(jié)點(diǎn)狀態(tài)設(shè)置為Singal,如果失敗則喚醒線程
        if (ws > 0 || !p.compareAndSetWaitStatus(ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }

該方法分為三個步驟:

  • (1)將該線程節(jié)點(diǎn)的狀態(tài)設(shè)置為初始狀態(tài)衰絮,如果失敗則表示當(dāng)前線程已經(jīng)中斷了袍冷,直接返回。
  • (2)通過enq(Node node)方法猫牡,將該線程節(jié)點(diǎn)放入同步隊列中胡诗。
  • (3)當(dāng)將該線程節(jié)點(diǎn)放入同步隊列后,獲取當(dāng)前節(jié)點(diǎn)的狀態(tài)并判斷淌友,如果該節(jié)點(diǎn)的waitStatus>0或者通過compareAndSetWaitStatus(ws, Node.SIGNAL)將該節(jié)點(diǎn)的狀態(tài)設(shè)置為Singal煌恢,如果失敗則通過LockSupport.unpark(node.thread)喚醒線程。

上述步驟中震庭,著重講enq(Node node)方法瑰抵,關(guān)于LockSupport.unPark(Thread thread)方法的理解,大家可以閱讀《Java并發(fā)編程之鎖機(jī)制之LockSupport工具》归薛。下面我們就來分析enq(Node node)方法。具體代碼如下所示:

   private Node enq(Node node) {
        for (;;) {
            //(1)獲取同步隊列的尾節(jié)點(diǎn)
            Node oldTail = tail;
            //(2)如果尾節(jié)點(diǎn)不為空匪蝙,則將該線程節(jié)點(diǎn)加入到同步隊列中
            if (oldTail != null) {
                //將當(dāng)前節(jié)點(diǎn)的prev指向尾節(jié)點(diǎn)
                U.putObject(node, Node.PREV, oldTail);
                //將同步隊列中的tail指針主籍,指向當(dāng)前節(jié)點(diǎn)
                if (compareAndSetTail(oldTail, node)) {
                    oldTail.next = node;
                    return oldTail;
                }
            } else {
                //(3)如果當(dāng)前同步隊列為空,則構(gòu)造同步隊列
                initializeSyncQueue();
            }
        }
    }

觀察該方法逛球,我們發(fā)現(xiàn)該方法通過死循環(huán)(當(dāng)然你也可以叫做自旋)的方式來添加該節(jié)點(diǎn)到同步隊列中去千元。該方法分為以下步驟:

  • (1)獲取同步隊列的尾節(jié)點(diǎn)
  • (2)如果尾節(jié)點(diǎn)不為空,則將該線程節(jié)點(diǎn)加入到同步隊列中
  • (3)如果當(dāng)前同步隊列為空颤绕,則通過initializeSyncQueue();構(gòu)造同步隊列幸海。

這里對Node enq(Node node)中的步驟(2)補(bǔ)充一個知識點(diǎn)。我們來看一下調(diào)用U.putObject(node, Node.PREV, oldTail);語句奥务,內(nèi)部是如何將當(dāng)前的節(jié)點(diǎn)的prev指向尾節(jié)點(diǎn)的物独。在AQS(AbstractQueuedSynchronizer)中的Node類中有如下靜態(tài)變量和語句。這里我省略了一下不重要的代碼氯葬。具體代碼如下所示:

private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
//省略部分代碼
static final long PREV;
    static {
         try {
            //省略部分代碼
            PREV = U.objectFieldOffset
               (Node.class.getDeclaredField("prev"));
              } catch (ReflectiveOperationException e) {
               throw new Error(e);
            }
        }
    }

其中Node.class.getDeclaredField("prev")語句很好理解挡篓,就是獲取Node類中pre字段,如果有則返回相應(yīng)Field字段帚称,反之拋出NoSuchFieldException異常官研。關(guān)于Unfase中的objectFieldOffset(Field f)方法,我曾經(jīng)在《Java并發(fā)編程之鎖機(jī)制之LockSupport工具》描述過類似的情況闯睹。這里我簡單的再解釋一遍戏羽。該方法用于獲取某個字段相對 Java對象的“起始地址”的偏移量,也就是說每個字段在類對應(yīng)的內(nèi)存中存儲是有“角標(biāo)”的楼吃,那么也就是說我們現(xiàn)在的PREV靜態(tài)變量就代表著Node中prev字段在內(nèi)存中的“角標(biāo)”始花。

當(dāng)獲取到"角標(biāo)"后妄讯,我們再通過U.putObject(node, Node.PREV, oldTail);該方法第一個參數(shù)是操作對象,第二個參數(shù)是操作的內(nèi)存“角標(biāo)”衙荐,第三個參數(shù)是期望值捞挥。那么最后,也就完成了將當(dāng)前節(jié)點(diǎn)的prev字段指向同步隊列的尾節(jié)點(diǎn)忧吟。

當(dāng)理解了該知識點(diǎn)后砌函,剩下的將同步隊列中的tail指針,指向當(dāng)前節(jié)點(diǎn)如果當(dāng)前同步隊列為空溜族,則構(gòu)造同步隊列這兩個操作就非常好理解了讹俊。由于篇幅的限制,在這里我就不在進(jìn)行描述了煌抒。希望讀者朋友們仍劈,能閱讀源代碼,舉一反三寡壮。關(guān)于這兩個方法的代碼如下所示:

   private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
    private static final long STATE;
    private static final long HEAD;
    private static final long TAIL;
    static {
        try {
            STATE = U.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("state"));
            HEAD = U.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("head"));
            TAIL = U.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
        } catch (ReflectiveOperationException e) {
            throw new Error(e);
        }
        Class<?> ensureLoaded = LockSupport.class;
    }

    private final void initializeSyncQueue() {
        Node h;
        if (U.compareAndSwapObject(this, HEAD, null, (h = new Node())))
            tail = h;
    }

    private final boolean compareAndSetTail(Node expect, Node update) {
        return U.compareAndSwapObject(this, TAIL, expect, update);
    }

喚醒流程

在理解了喚醒的具體邏輯后贩疙,現(xiàn)在來總結(jié)一下,喚醒的具體流程况既。具體如下圖所示:


喚醒流程.png
  • 將等待隊列中的節(jié)點(diǎn)線程这溅,移動到同步隊列中。
  • 當(dāng)移動到同步隊列中后棒仍。喚醒該線程悲靴。讓該線程參與同步狀態(tài)的競爭。

整體流程其實(shí)不算太復(fù)雜莫其,大家只需要注意癞尚,當(dāng)我們將等待隊列中的線程節(jié)點(diǎn)加入到同步隊列之后,才會喚醒線程乱陡。

最后

該文章參考以下圖書浇揩,站在巨人的肩膀上『┑撸可以看得更遠(yuǎn)临燃。

  • 《Java并發(fā)編程的藝術(shù)》

推薦閱讀

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市烙心,隨后出現(xiàn)的幾起案子膜廊,更是在濱河造成了極大的恐慌,老刑警劉巖淫茵,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件爪瓜,死亡現(xiàn)場離奇詭異,居然都是意外死亡匙瘪,警方通過查閱死者的電腦和手機(jī)铆铆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進(jìn)店門蝶缀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人薄货,你說我怎么就攤上這事翁都。” “怎么了谅猾?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵柄慰,是天一觀的道長。 經(jīng)常有香客問我税娜,道長坐搔,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任敬矩,我火速辦了婚禮概行,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘弧岳。我一直安慰自己凳忙,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布禽炬。 她就那樣靜靜地躺著涧卵,像睡著了一般。 火紅的嫁衣襯著肌膚如雪瞎抛。 梳的紋絲不亂的頭發(fā)上艺演,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天却紧,我揣著相機(jī)與錄音桐臊,去河邊找鬼。 笑死晓殊,一個胖子當(dāng)著我的面吹牛断凶,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播巫俺,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼认烁,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了介汹?” 一聲冷哼從身側(cè)響起却嗡,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎嘹承,沒想到半個月后窗价,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡叹卷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年撼港,在試婚紗的時候發(fā)現(xiàn)自己被綠了坪它。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡帝牡,死狀恐怖往毡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情靶溜,我是刑警寧澤开瞭,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站墨技,受9級特大地震影響惩阶,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜扣汪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一断楷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧崭别,春花似錦冬筒、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至诀姚,卻和暖如春响牛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背赫段。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工呀打, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人糯笙。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓贬丛,卻偏偏與公主長得像,于是被迫代替她去往敵國和親给涕。 傳聞我的和親對象是個殘疾皇子豺憔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評論 2 355

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