JUC之玩转Condition

每期总结一个小的知识点和相关面试题,嘿嘿,又来和大家共同学习了。

GUC中有个类我们用的比较少,但是他确是很多类中不可或缺的成员。他就是Condition。

从字面意思理解就是条件,那条件的话就有true or false。那Condition是起到一个
多线程共享标识位执行阻塞的作用,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()://唤醒

假设线程1和线程2,并发执行。那么执行的后输出会是:

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

发现没有,是不是和一个Object对象的wait(),notify()很像。唯一的区别是Condition不需要先
synchronize修饰后才能调用阻塞方法。那是不是使用起来更方便了。像阻塞队列里面empty和full的判断
都是基于Condition来实现的,可以保证通知顺序。

2、Condition的原理

一个Condition实例本质上绑定到一个锁。 要获得特定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()导致当前线程等到发信号或 interrupted
boolean await(long time, TimeUnit unit)使当前线程等待直到发出信号或中断,或指定的等待时间过去。
long awaitNanos(long nanosTimeout)使当前线程等待直到发出信号或中断,或指定的等待时间过去。
void awaitUninterruptibly()使当前线程等待直到发出信号。
boolean awaitUntil(Date deadline)使当前线程等待直到发出信号或中断,或者指定的最后期限过去。
void signal()唤醒一个等待线程。
void signalAll()唤醒所有等待线程。

2.1 Condition实现

初始化方法:

final ConditionObject newCondition() {
  // ConditionObject是AQS的内部类,内部类当中可以调用外部类当中的属性和方法
  return new ConditionObject();
}

首先看下await方法,如何实现阻塞等待:

public final void await() throws InterruptedException {
    // 如果当前线程被中断,则抛出 InterruptedException
    if (Thread.interrupted())
        throw new InterruptedException();
    // 添加一个等待node,可以看出来Condition就是对AQS的node节点的各种判断
    Node node = addConditionWaiter();
    // 用node当前状态值调用释放;返回保存状态
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    // 是在同步队列?isOnSyncQueue在Node的next不为空是返回true,什么意思就是非第一个LCH节点就会执行线程阻塞。
    while (!isOnSyncQueue(node)) {
        // 当前线程阻塞
        LockSupport.park(this);
        // 检查是否中断
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    // 中断状态的处理
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    // 节点清理
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    // 0是默认状态
    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;
    }
    // 创建一个condition状态的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如何实现唤醒Node:

public final void signal() {
    // 判断是否有线程执行权限,lock调用线程才有权限,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;
    // 将节点从条件队列转移到同步队列。
    } 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相关面试题

3.1、什么是Java虚假唤醒及如何避免虚假唤醒?

虚假唤醒

当一个条件满足时,很多线程都被唤醒了,但是只有其中部分是有用的唤醒,其它的唤醒都是无用功

比如说买货,如果商品本来没有货物,突然进了一件商品,这是所有的线程都被唤醒了,但是只能一个人买,所以其他人都是假唤醒,获取不到对象的锁

如何避免虚假唤醒

所有的线程都被唤醒了的时候,判断临界条件使用while判断,这样在被唤醒的时候,可以再check一次条件。

3.2、Mutex、BooleanLatch 什么场景使用

Mutex:这是一个不可重入互斥锁类,它使用零值来表示解锁状态,一个表示锁定状态。 虽然不可重入锁不严格要求记录当前的所有者线程,但是这样做无论如何使得使用更容易监视。 它还支持条件并公开其中一种仪器方法

BooleanLatch:这是一个类似CountDownLatch的闩锁类,只是它只需要一个signal才能触发

3.3、CLH锁和MCS锁的差异

  • 从代码实现来看,CLH比MCS要简单得多。
  • 从自旋的条件来看,CLH是在前驱节点的属性上自旋,而MCS是在本地属性变量上自旋。
  • 从链表队列来看,CLHNode不直接持有前驱节点,CLH锁释放时只需要改变自己的属性;MCSNode直接持有后继节点,MCS锁释放需要改变后继节点的属性。
  • CLH锁释放时只需要改变自己的属性,MCS锁释放则需要改变后继节点的属性

3.4、Node的状态有哪些

  • CANCELLED(1):表示当前结点已取消调度。当timeout或被中断(响应中断的情况下),会触发变更为此状态,进入该状态后的结点将不会再变化。
  • SIGNAL(-1):表示后继结点在等待当前结点唤醒。后继结点入队时,会将前继结点的状态更新为SIGNAL。
  • CONDITION(-2):表示结点等待在Condition上,当其他线程调用了Condition的signal()方法后,CONDITION状态的结点将从等待队列转移到同步队列中,等待获取同步锁。
  • PROPAGATE(-3):共享模式下,前继结点不仅会唤醒其后继结点,同时也可能会唤醒后继的后继结点。
  • 0:新结点入队时的默认状态。

本文由猿必过 YBG 发布
禁止未经授权转载,违者依法追究相关法律责任
如需授权可联系:zhuyunhui@yuanbiguo.com

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352

推荐阅读更多精彩内容