AQS之阻塞和喚醒線程

在前面的文章中介紹了獨(dú)占式同步狀態(tài)的獲取和釋放以及共享式同步狀態(tài)的獲取和釋放,在前面的文章中并沒有介紹線程的阻塞和喚醒,在這篇文章中LZ將介紹在AQS中線程的阻塞和喚醒。
在線程獲取同步狀態(tài)失敗后,會(huì)加入到CHL隊(duì)列中去,并且該節(jié)點(diǎn)會(huì)自旋式的不斷的獲取同步狀態(tài),在獲取同步狀態(tài)的過程中,需要判斷當(dāng)前線程是否需要被阻塞。其主要方法在acquireQueued(final Node node, int arg)方法的定義里面:

if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
    interrupted = true;

通過這段代碼可以看出,線程在獲取同步狀態(tài)失敗后,并不是立馬進(jìn)入等待狀態(tài),而是需要判斷當(dāng)前線程是否需要被阻塞。檢查是否需要阻塞的方法shouldParkAfterFailedAcquire(p, node),其定義如下:

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    // 獲取前驅(qū)節(jié)點(diǎn)的等待狀態(tài)
    int ws = pred.waitStatus;
    // 若果等待狀態(tài)的值為SIGNAL,則返回true 表示當(dāng)前線程需要等待
    if (ws == Node.SIGNAL)
        return true;
    if (ws > 0) {
        /*
         * 前驅(qū)節(jié)點(diǎn)的狀態(tài)>0,為CANCLE狀態(tài),表示該節(jié)點(diǎn)被中斷或者超時(shí),需要
         * 從CHL中移除。
         */
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
         /* 前驅(qū)節(jié)點(diǎn)為 PROPAGATE或者CONDITION 將前驅(qū)節(jié)點(diǎn)的等待狀態(tài)以CAS的方式
         * 更新為SIGNAL
         */
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

上面這段代碼主要的功能就是判斷當(dāng)前線程是否需要阻塞,當(dāng)該方法的返回值為true時(shí),表示當(dāng)前線程需要等待,反之返回false.其規(guī)則如下:

  1. 如果當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)的等待狀態(tài)為SIGNAL,則返回true
  2. 如果當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)的等待狀態(tài)為CALCLE,則表示該線程的前驅(qū)節(jié)點(diǎn)已經(jīng)被中斷或者超時(shí),需要從CHL中刪除,直到回溯到ws <= 0,返回false
  3. 若果當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)的等待狀態(tài)為非SIGNAL,非CANCLE,則以CAS的方式設(shè)置其前驅(qū)節(jié)點(diǎn)為的狀態(tài)為SIGNAL,返回false.

當(dāng) shouldParkAfterFailedAcquire(Node pred, Node node)方法返回true時(shí),會(huì)執(zhí)行 parkAndCheckInterrupt()方法。該方法定義如下:

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

該方法就實(shí)現(xiàn)了將線程掛起,從而阻塞住線程的調(diào)用棧,已達(dá)到阻塞線程的目的。其內(nèi)部則是調(diào)用了LockSupport工具類的park()方法來實(shí)現(xiàn)的。

當(dāng)同步狀態(tài)被釋放后,需要喚醒后繼節(jié)點(diǎn):

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            // 喚醒后繼節(jié)點(diǎn)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

unparkSuccessor()方法的定義如下:

private void unparkSuccessor(Node node) {
    // 當(dāng)前節(jié)點(diǎn)的等待狀態(tài)
    int ws = node.waitStatus;
    // 當(dāng)前節(jié)點(diǎn)狀態(tài) < 0,則CAS方法設(shè)置當(dāng)前狀態(tài)為0
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    // 當(dāng)前節(jié)點(diǎn)的后繼節(jié)點(diǎn)
    Node s = node.next;
    //  如果后繼節(jié)點(diǎn)為空或者后繼節(jié)點(diǎn)的等待狀態(tài)為CANCLE
    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;
    }
    // 喚醒后繼節(jié)點(diǎn)
    if (s != null)
        LockSupport.unpark(s.thread);
}

在這里很多人大概會(huì)有疑問為什么是從尾部回溯找到一個(gè)可用的節(jié)點(diǎn),我們不妨先來回顧下添加節(jié)點(diǎn)的方法,

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;
            }
        }
    }
}

在上面代碼中我們可以看到,將節(jié)點(diǎn)添加到尾部是一個(gè)CAS操作,但是t.next = node 這個(gè)操作不是線程安全的,如果一個(gè)線程在執(zhí)行CAS添加尾部之后正好有線程釋放了同步狀態(tài),這個(gè)時(shí)候如果是從head到tail的遍歷,則會(huì)出現(xiàn)中間斷裂的情況,而從尾部回溯是一定可以遍歷到所有節(jié)點(diǎn)的。
上面線程的喚醒和等待都是通過LockSupport工具類中的方法來實(shí)現(xiàn)的,我們來看看LockSupport這個(gè)工具類的。

LockSupport

1.LockSupport介紹

LockSupport是用于創(chuàng)建鎖和其他同步類的基本線程阻塞原語。
LockSupport定義了一組以park開頭的方法用來阻塞線程,以及以u(píng)npark(Thread thread)方法來喚醒一個(gè)線程。park方法和unpark方法提供了阻止和解除阻塞線程的有效手段,該方法不會(huì)遇到Threaad.suspend和Thread.resum方法導(dǎo)致的死鎖問題。

2.LockSupport方法列表
  • getBlocker(Thread t) :返回提供給最近調(diào)用尚未解除阻塞的park 方法調(diào)用的 blocker 對(duì)象,如果調(diào)用不阻止,則返回null
  • park() : 禁止當(dāng)前線程進(jìn)行線程調(diào)度,除非許可證可用。
  • park(Object blocker) :禁止當(dāng)前線程進(jìn)行線程調(diào)度,除非許可證可用。
  • parkNanos(long nanos) :禁止當(dāng)前線程進(jìn)行線程調(diào)度,直到指定的等待時(shí)間,除非許可證可用。
  • parkNanos(Object blocker, long nanos) :禁止當(dāng)前線程進(jìn)行線程調(diào)度,直到指定的等待時(shí)間,除非許可證可用。
  • parkUntil(long deadline) :禁止當(dāng)前線程進(jìn)行線程調(diào)度,直到指定的截止時(shí)間,除非許可證可用。
  • parkUntil(Object blocker, long deadline) :禁止當(dāng)前線程進(jìn)行線程調(diào)度,直到指定的截止時(shí)間,除非許可證可用。
  • unpark(Thread thread) :為給定的線程提供許可證(如果尚未提供)。

上述方法中參數(shù) blocker 是用來標(biāo)識(shí)當(dāng)前線程在等待的對(duì)象,改對(duì)象主要用于問題的排查和系統(tǒng)給的監(jiān)控。

接下來我們?cè)诳纯磒ark和unpark方法的定義:
park:

public static void park() {
    UNSAFE.park(false, 0 L);
}

unpark:

public static void unpark(Thread thread) {
    if (thread != null)
        UNSAFE.unpark(thread);
}

從上面方法的定義中我們可以看出park和unpark方法都是通過UNSAFE類中的park和unpark方法來實(shí)現(xiàn)的。其UNSAFE中park和unpark的方法定義如下:

public native void unpark(Object var1);

public native void park(boolean var1, long var2);

可以看出這2個(gè)方法都是本地方法。Unsafe是一個(gè)不安全的類,主要用于執(zhí)行低級(jí)別、不安全的方法集合。盡管Unsafe類中的方法都是public的,但是我們還是不能在自己的java代碼中調(diào)用這個(gè)類中的方法,因?yàn)橹挥惺谛诺拇a才能獲取到該類的實(shí)例。

掃碼關(guān)注公眾號(hào),回復(fù)1024 獲取最新大廠面試資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 基于jdk8 越是核心的東西越是要反復(fù)看,本文篇幅較長(zhǎng),希望各位細(xì)細(xì)品讀,來回多讀幾遍理解下。 AQS簡(jiǎn)介 ??j...
    劍書藏于西閱讀 489評(píng)論 0 2
  • AQS簡(jiǎn)介 AQS:AbstractQueuedSynchronizer,即隊(duì)列同步器。它是構(gòu)建鎖或者其他同步組件...
    lijiaccy閱讀 667評(píng)論 0 0
  • ReentrantLock 介紹 一個(gè)可重入的互斥鎖,它具有與使用{synchronized}方法和語句訪問的隱式...
    tomas家的小撥浪鼓閱讀 4,253評(píng)論 1 4
  • 1 AQS是什么? 通過JCP的JSR166規(guī)范,Jdk1.5開始引入了j.u.c包,這個(gè)包提供了一系列支持并發(fā)的...
    攻城獅哦哦也閱讀 533評(píng)論 1 0
  • 本文的宗旨在于鼓勵(lì)所有有條件的妹紙們順產(chǎn)! 我想這世界唯一一件經(jīng)歷的時(shí)候特別痛苦,但是回憶的時(shí)候特別特別幸福的事情...
    愛折騰雪梨閱讀 1,090評(píng)論 0 0

友情鏈接更多精彩內(nèi)容