美文网首页
JUC之玩转Condition

JUC之玩转Condition

作者: 猿必过 | 来源:发表于2021-03-28 19:46 被阅读0次

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

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

相关文章

  • JUC之玩转Condition

    每期总结一个小的知识点和相关面试题,嘿嘿,又来和大家共同学习了。 GUC中有个类我们用的比较少,但是他确是很多类中...

  • JUC之Condition源码分析

    原文出处:https://www.zzwzdx.cn Condition接口定义了类似Object的监视器方法,它...

  • JUC--Condition

    2018-10-01 原文推荐 死磕Java并发 Condition提供了一系列的方法来对阻塞和唤醒线程:awa...

  • Condition 源码分析 (基于Java 8)

    1. Condition 定义 Condition是JUC里面提供于控制线程释放锁, 然后进行等待其他获取锁的线程...

  • Condition原理和源码解析

    Condition 定义Condition是JUC里面提供于控制线程释放锁, 然后进行等待其他获取锁的线程发送 s...

  • juc-locks框架之接口

    juc-locks锁框架中一共就三个接口:Lock、Condition、ReadWriteLock 一、Lock接...

  • JUC常见并发工具

    1.Condition JUC Lock 线程间通信工具类 执行结果: 流程图image.png 2.CountD...

  • JUC-(10)AQS(上)-独占模式

    AQS(上)-独占模式 AQS(中)-共享模式 AQS(下)-Condition 简叙 之前我写过很多关于JUC下...

  • JUC (06)ReentrantLock-Condition

    Condition 利用锁可以让线程以同步的方式来执行一段代码,而Condition则是用来实现线程之间协作的。 ...

  • 知识梳理目录

    Java基础 Java线程池 AQS之独占锁 AQS之Condition AQS之Condition AQS之同步...

网友评论

      本文标题:JUC之玩转Condition

      本文链接:https://www.haomeiwen.com/subject/uyhmhltx.html