每期总结一个小的知识点和相干面试题,嘿嘿,又来和大家独特学习了。
GUC中有个类咱们用的比拟少,然而他确是很多类中不可或缺的成员。他就是Condition。
从字面意思了解就是条件,那条件的话就有true
or false
。那Condition是起到一个
多线程共享标识位执行阻塞的作用,true
的时候通过, false
的时候期待。
1、Condition的应用
通过上面的一个代码能够看进去如何应用它。
// thread 1System.out.println("1 am thread 1 start");condition.await();//阻塞System.out.println("1 am thread 1 end");// thread 2System.out.println("1 am thread 2");condition.signal()://唤醒
假如线程1和线程2,并发执行。那么执行的后输入会是:
1 am thread 1 start1 am thread 21 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 公布