关于java:JUC之玩转Condition

56次阅读

共计 4446 个字符,预计需要花费 12 分钟才能阅读完成。

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

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

正文完
 0