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