手记

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:新结点入队时的默认状态。
0人推荐
随时随地看视频
慕课网APP