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 发布

你可能感兴趣的:(java)