每期總結(jié)一個小的知識點和相關(guān)面試題函匕,嘿嘿,又來和大家共同學(xué)習(xí)了蚪黑。
GUC中有個類我們用的比較少盅惜,但是他確是很多類中不可或缺的成員。他就是Condition忌穿。
從字面意思理解就是條件抒寂,那條件的話就有true
or false
。那Condition是起到一個
多線程共享標識位執(zhí)行阻塞的作用掠剑,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()://喚醒
假設(shè)線程1和線程2属铁,并發(fā)執(zhí)行。那么執(zhí)行的后輸出會是:
1 am thread 1 start
1 am thread 2
1 am thread 1 end
發(fā)現(xiàn)沒有躬翁,是不是和一個Object對象的wait(),notify()很像焦蘑。唯一的區(qū)別是Condition不需要先
synchronize修飾后才能調(diào)用阻塞方法。那是不是使用起來更方便了姆另。像阻塞隊列里面empty和full的判斷
都是基于Condition來實現(xiàn)的喇肋,可以保證通知順序。
2迹辐、Condition的原理
一個Condition實例本質(zhì)上綁定到一個鎖蝶防。 要獲得特定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() 導(dǎo)致當(dāng)前線程等到發(fā)信號或 interrupted 间学。 |
boolean |
await(long time, TimeUnit unit) 使當(dāng)前線程等待直到發(fā)出信號或中斷,或指定的等待時間過去印荔。 |
long |
awaitNanos(long nanosTimeout) 使當(dāng)前線程等待直到發(fā)出信號或中斷低葫,或指定的等待時間過去。 |
void |
awaitUninterruptibly() 使當(dāng)前線程等待直到發(fā)出信號仍律。 |
boolean |
awaitUntil(Date deadline) 使當(dāng)前線程等待直到發(fā)出信號或中斷嘿悬,或者指定的最后期限過去。 |
void |
signal() 喚醒一個等待線程水泉。 |
void |
signalAll() 喚醒所有等待線程善涨。 |
2.1 Condition實現(xiàn)
初始化方法:
final ConditionObject newCondition() {
// ConditionObject是AQS的內(nèi)部類,內(nèi)部類當(dāng)中可以調(diào)用外部類當(dāng)中的屬性和方法
return new ConditionObject();
}
首先看下await方法草则,如何實現(xiàn)阻塞等待:
public final void await() throws InterruptedException {
// 如果當(dāng)前線程被中斷钢拧,則拋出 InterruptedException
if (Thread.interrupted())
throw new InterruptedException();
// 添加一個等待node,可以看出來Condition就是對AQS的node節(jié)點的各種判斷
Node node = addConditionWaiter();
// 用node當(dāng)前狀態(tài)值調(diào)用釋放炕横;返回保存狀態(tài)
int savedState = fullyRelease(node);
int interruptMode = 0;
// 是在同步隊列源内?isOnSyncQueue在Node的next不為空是返回true,什么意思就是非第一個LCH節(jié)點就會執(zhí)行線程阻塞份殿。
while (!isOnSyncQueue(node)) {
// 當(dāng)前線程阻塞
LockSupport.park(this);
// 檢查是否中斷
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 中斷狀態(tài)的處理
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
// 節(jié)點清理
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
// 0是默認狀態(tài)
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;
}
// 創(chuàng)建一個condition狀態(tài)的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如何實現(xiàn)喚醒Node:
public final void signal() {
// 判斷是否有線程執(zhí)行權(quán)限膜钓,lock調(diào)用線程才有權(quán)限,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;
// 將節(jié)點從條件隊列轉(zhuǎn)移到同步隊列卿嘲。
} 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相關(guān)面試題
3.1、什么是Java虛假喚醒及如何避免虛假喚醒腔寡?
虛假喚醒
當(dāng)一個條件滿足時焚鲜,很多線程都被喚醒了掌唾,但是只有其中部分是有用的喚醒放前,其它的喚醒都是無用功
比如說買貨忿磅,如果商品本來沒有貨物,突然進了一件商品凭语,這是所有的線程都被喚醒了葱她,但是只能一個人買,所以其他人都是假喚醒似扔,獲取不到對象的鎖
如何避免虛假喚醒
所有的線程都被喚醒了的時候吨些,判斷臨界條件使用while判斷,這樣在被喚醒的時候炒辉,可以再check一次條件豪墅。
3.2、Mutex黔寇、BooleanLatch 什么場景使用
Mutex:這是一個不可重入互斥鎖類偶器,它使用零值來表示解鎖狀態(tài),一個表示鎖定狀態(tài)缝裤。 雖然不可重入鎖不嚴格要求記錄當(dāng)前的所有者線程屏轰,但是這樣做無論如何使得使用更容易監(jiān)視。 它還支持條件并公開其中一種儀器方法
BooleanLatch:這是一個類似CountDownLatch的閂鎖類憋飞,只是它只需要一個signal才能觸發(fā)
3.3霎苗、CLH鎖和MCS鎖的差異
- 從代碼實現(xiàn)來看,CLH比MCS要簡單得多榛做。
- 從自旋的條件來看唁盏,CLH是在前驅(qū)節(jié)點的屬性上自旋,而MCS是在本地屬性變量上自旋瘤睹。
- 從鏈表隊列來看升敲,CLHNode不直接持有前驅(qū)節(jié)點,CLH鎖釋放時只需要改變自己的屬性轰传;MCSNode直接持有后繼節(jié)點驴党,MCS鎖釋放需要改變后繼節(jié)點的屬性。
- CLH鎖釋放時只需要改變自己的屬性获茬,MCS鎖釋放則需要改變后繼節(jié)點的屬性
3.4港庄、Node的狀態(tài)有哪些
- CANCELLED(1):表示當(dāng)前結(jié)點已取消調(diào)度。當(dāng)timeout或被中斷(響應(yīng)中斷的情況下)恕曲,會觸發(fā)變更為此狀態(tài)鹏氧,進入該狀態(tài)后的結(jié)點將不會再變化。
- SIGNAL(-1):表示后繼結(jié)點在等待當(dāng)前結(jié)點喚醒佩谣。后繼結(jié)點入隊時把还,會將前繼結(jié)點的狀態(tài)更新為SIGNAL。
- CONDITION(-2):表示結(jié)點等待在Condition上,當(dāng)其他線程調(diào)用了Condition的signal()方法后吊履,CONDITION狀態(tài)的結(jié)點將從等待隊列轉(zhuǎn)移到同步隊列中安皱,等待獲取同步鎖。
- PROPAGATE(-3):共享模式下艇炎,前繼結(jié)點不僅會喚醒其后繼結(jié)點酌伊,同時也可能會喚醒后繼的后繼結(jié)點。
- 0:新結(jié)點入隊時的默認狀態(tài)缀踪。
本文由猿必過 YBG 發(fā)布
禁止未經(jīng)授權(quán)轉(zhuǎn)載居砖,違者依法追究相關(guān)法律責(zé)任
如需授權(quán)可聯(lián)系:zhuyunhui@yuanbiguo.com