栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

JUC之玩转Condition

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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 (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
 return false;

    
    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:新结点入队时的默认状态。
转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号