JUC之玩轉(zhuǎn)Condition

每期總結(jié)一個小的知識點和相關(guān)面試題函匕,嘿嘿,又來和大家共同學(xué)習(xí)了蚪黑。

GUC中有個類我們用的比較少盅惜,但是他確是很多類中不可或缺的成員。他就是Condition忌穿。

從字面意思理解就是條件抒寂,那條件的話就有true or false。那Condition是起到一個
多線程共享標識位執(zhí)行阻塞的作用掠剑,true 的時候通過屈芜, false 的時候等待。

1、Condition的使用

通過下面的一個代碼可以看出來如何使用它井佑。

// thread 1
System.out.println("1 am thread 1 start");
condition.await();//阻塞
System.out.println("1 am thread 1 end");

// thread 2
System.out.println("1 am thread 2");
condition.signal()://喚醒

假設(shè)線程1和線程2属铁,并發(fā)執(zhí)行。那么執(zhí)行的后輸出會是:

1 am thread 1 start
1 am thread 2
1 am thread 1 end

發(fā)現(xiàn)沒有躬翁,是不是和一個Object對象的wait(),notify()很像焦蘑。唯一的區(qū)別是Condition不需要先
synchronize修飾后才能調(diào)用阻塞方法。那是不是使用起來更方便了姆另。像阻塞隊列里面empty和full的判斷
都是基于Condition來實現(xiàn)的喇肋,可以保證通知順序。

2迹辐、Condition的原理

一個Condition實例本質(zhì)上綁定到一個鎖蝶防。 要獲得特定Condition實例的Condition實例,請使用其newCondition()方法明吩。

   final Lock lock = new ReentrantLock();
   // 需要綁定到lock
   final Condition notFull  = lock.newCondition(); 
   final Condition notEmpty = lock.newCondition(); 

2.1 Condition API

Modifier and Type Method and Description
void await()導(dǎo)致當(dāng)前線程等到發(fā)信號或 interrupted 间学。
boolean await(long time, TimeUnit unit)使當(dāng)前線程等待直到發(fā)出信號或中斷,或指定的等待時間過去印荔。
long awaitNanos(long nanosTimeout)使當(dāng)前線程等待直到發(fā)出信號或中斷低葫,或指定的等待時間過去。
void awaitUninterruptibly()使當(dāng)前線程等待直到發(fā)出信號仍律。
boolean awaitUntil(Date deadline)使當(dāng)前線程等待直到發(fā)出信號或中斷嘿悬,或者指定的最后期限過去。
void signal()喚醒一個等待線程水泉。
void signalAll()喚醒所有等待線程善涨。

2.1 Condition實現(xiàn)

初始化方法:

final ConditionObject newCondition() {
  // ConditionObject是AQS的內(nèi)部類,內(nèi)部類當(dāng)中可以調(diào)用外部類當(dāng)中的屬性和方法
  return new ConditionObject();
}

首先看下await方法草则,如何實現(xiàn)阻塞等待:

public final void await() throws InterruptedException {
    // 如果當(dāng)前線程被中斷钢拧,則拋出 InterruptedException
    if (Thread.interrupted())
        throw new InterruptedException();
    // 添加一個等待node,可以看出來Condition就是對AQS的node節(jié)點的各種判斷
    Node node = addConditionWaiter();
    // 用node當(dāng)前狀態(tài)值調(diào)用釋放炕横;返回保存狀態(tài)
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    // 是在同步隊列源内?isOnSyncQueue在Node的next不為空是返回true,什么意思就是非第一個LCH節(jié)點就會執(zhí)行線程阻塞份殿。
    while (!isOnSyncQueue(node)) {
        // 當(dāng)前線程阻塞
        LockSupport.park(this);
        // 檢查是否中斷
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    // 中斷狀態(tài)的處理
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    // 節(jié)點清理
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    // 0是默認狀態(tài)
    if (interruptMode != 0)
        // interrupt處理
        reportInterruptAfterWait(interruptMode);
}

private Node addConditionWaiter() {
    Node t = lastWaiter;
    // If lastWaiter is cancelled, clean out.
    if (t != null && t.waitStatus != Node.CONDITION) {
        unlinkCancelledWaiters();
        t = lastWaiter;
    }
    // 創(chuàng)建一個condition狀態(tài)的Node
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)
        firstWaiter = node;
    else
        t.nextWaiter = node;
    lastWaiter = node;
    return node;
}

final int fullyRelease(Node node) {
    boolean failed = true;
    try {
        int savedState = getState();
        if (release(savedState)) {
            failed = false;
            return savedState;
        } else {
            throw new IllegalMonitorStateException();
        }
    } finally {
        if (failed)
            node.waitStatus = Node.CANCELLED;
    }
}

那么再看下signal如何實現(xiàn)喚醒Node:

public final void signal() {
    // 判斷是否有線程執(zhí)行權(quán)限膜钓,lock調(diào)用線程才有權(quán)限,getExclusiveOwnerThread() == Thread.currentThread();
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    // 存在等待的node才需要喚醒
    if (first != null)
        doSignal(first);
}

private void doSignal(Node first) {
    do {
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    // 將節(jié)點從條件隊列轉(zhuǎn)移到同步隊列卿嘲。
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}


final boolean transferForSignal(Node node) {
    /*
     * If cannot change waitStatus, the node has been cancelled.
     */
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;

    /*
     * Splice onto queue and try to set waitStatus of predecessor to
     * indicate that thread is (probably) waiting. If cancelled or
     * attempt to set waitStatus fails, wake up to resync (in which
     * case the waitStatus can be transiently and harmlessly wrong).
     */
    Node p = enq(node);
    int ws = p.waitStatus;
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        // unpark喚醒線程
        LockSupport.unpark(node.thread);
    return true;
}

3颂斜、Condition相關(guān)面試題

3.1、什么是Java虛假喚醒及如何避免虛假喚醒腔寡?

虛假喚醒

當(dāng)一個條件滿足時焚鲜,很多線程都被喚醒了掌唾,但是只有其中部分是有用的喚醒放前,其它的喚醒都是無用功

比如說買貨忿磅,如果商品本來沒有貨物,突然進了一件商品凭语,這是所有的線程都被喚醒了葱她,但是只能一個人買,所以其他人都是假喚醒似扔,獲取不到對象的鎖

如何避免虛假喚醒

所有的線程都被喚醒了的時候吨些,判斷臨界條件使用while判斷,這樣在被喚醒的時候炒辉,可以再check一次條件豪墅。

3.2、Mutex黔寇、BooleanLatch 什么場景使用

Mutex:這是一個不可重入互斥鎖類偶器,它使用零值來表示解鎖狀態(tài),一個表示鎖定狀態(tài)缝裤。 雖然不可重入鎖不嚴格要求記錄當(dāng)前的所有者線程屏轰,但是這樣做無論如何使得使用更容易監(jiān)視。 它還支持條件并公開其中一種儀器方法

BooleanLatch:這是一個類似CountDownLatch的閂鎖類憋飞,只是它只需要一個signal才能觸發(fā)

3.3霎苗、CLH鎖和MCS鎖的差異

  • 從代碼實現(xiàn)來看,CLH比MCS要簡單得多榛做。
  • 從自旋的條件來看唁盏,CLH是在前驅(qū)節(jié)點的屬性上自旋,而MCS是在本地屬性變量上自旋瘤睹。
  • 從鏈表隊列來看升敲,CLHNode不直接持有前驅(qū)節(jié)點,CLH鎖釋放時只需要改變自己的屬性轰传;MCSNode直接持有后繼節(jié)點驴党,MCS鎖釋放需要改變后繼節(jié)點的屬性。
  • CLH鎖釋放時只需要改變自己的屬性获茬,MCS鎖釋放則需要改變后繼節(jié)點的屬性

3.4港庄、Node的狀態(tài)有哪些

  • CANCELLED(1):表示當(dāng)前結(jié)點已取消調(diào)度。當(dāng)timeout或被中斷(響應(yīng)中斷的情況下)恕曲,會觸發(fā)變更為此狀態(tài)鹏氧,進入該狀態(tài)后的結(jié)點將不會再變化。
  • SIGNAL(-1):表示后繼結(jié)點在等待當(dāng)前結(jié)點喚醒佩谣。后繼結(jié)點入隊時把还,會將前繼結(jié)點的狀態(tài)更新為SIGNAL。
  • CONDITION(-2):表示結(jié)點等待在Condition上,當(dāng)其他線程調(diào)用了Condition的signal()方法后吊履,CONDITION狀態(tài)的結(jié)點將從等待隊列轉(zhuǎn)移到同步隊列中安皱,等待獲取同步鎖。
  • PROPAGATE(-3):共享模式下艇炎,前繼結(jié)點不僅會喚醒其后繼結(jié)點酌伊,同時也可能會喚醒后繼的后繼結(jié)點。
  • 0:新結(jié)點入隊時的默認狀態(tài)缀踪。

本文由猿必過 YBG 發(fā)布
禁止未經(jīng)授權(quán)轉(zhuǎn)載居砖,違者依法追究相關(guān)法律責(zé)任
如需授權(quán)可聯(lián)系:zhuyunhui@yuanbiguo.com

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市驴娃,隨后出現(xiàn)的幾起案子奏候,更是在濱河造成了極大的恐慌,老刑警劉巖唇敞,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鼻由,死亡現(xiàn)場離奇詭異,居然都是意外死亡厚棵,警方通過查閱死者的電腦和手機蕉世,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來婆硬,“玉大人狠轻,你說我怎么就攤上這事”蚍福” “怎么了向楼?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長谐区。 經(jīng)常有香客問我湖蜕,道長,這世上最難降的妖魔是什么宋列? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任昭抒,我火速辦了婚禮,結(jié)果婚禮上炼杖,老公的妹妹穿的比我還像新娘灭返。我一直安慰自己,他們只是感情好坤邪,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布熙含。 她就那樣靜靜地躺著,像睡著了一般艇纺。 火紅的嫁衣襯著肌膚如雪怎静。 梳的紋絲不亂的頭發(fā)上邮弹,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天,我揣著相機與錄音蚓聘,去河邊找鬼肠鲫。 笑死,一個胖子當(dāng)著我的面吹牛或粮,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播捞高,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼氯材,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了硝岗?” 一聲冷哼從身側(cè)響起氢哮,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎型檀,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡朦前,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年唬格,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片仓坞。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡背零,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出无埃,到底是詐尸還是另有隱情徙瓶,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布嫉称,位于F島的核電站侦镇,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏织阅。R本人自食惡果不足惜壳繁,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望荔棉。 院中可真熱鬧氮趋,春花似錦、人聲如沸江耀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽祥国。三九已至昵观,卻和暖如春晾腔,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背啊犬。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工灼擂, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人觉至。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓剔应,卻偏偏與公主長得像,于是被迫代替她去往敵國和親语御。 傳聞我的和親對象是個殘疾皇子峻贮,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354

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