java设置等待锁的时间_java的锁池和等待池
謝邀。不知道題中的一段文字出自何處。“鎖池”和“等待池”這種翻譯我還是頭一回見。不過,題主的思路已經對了,即不拘泥于文字,而是在考慮這兩個東西在鎖的調度(即決定哪個線程可以獲得鎖的過程)中起到什么作用。
Java平臺中,每個對象都有一個唯一與之對應的內部鎖(Monitor)。Java虛擬機會為每個對象維護兩個“隊列”(姑且稱之為“隊列”,盡管它不一定符合數據結構上隊列的“先進先出”原則):一個叫Entry Set(入口集),另外一個叫Wait Set(等待集)。對于任意的對象objectX,objectX的Entry Set用于存儲等待獲取objectX對應的內部鎖的所有線程。objectX的Wait Set用于存儲執行了objectX.wait()/wait(long)的線程。
設objectX是任意一個對象,monitorX是這個對象對應的內部鎖,假設有線程A、B、C同時申請monitorX,那么由于任意一個時刻只有一個線程能夠獲得(占用/持有)這個鎖,因此除了勝出(即獲得了鎖)的線程(這里假設是B)外,其他線程(這里就是A和C)都會被暫停(線程的生命周期狀態會被調整為BLOCKED)。這些因申請鎖而落選的線程就會被存入objectX對應的Entry Set(以下記為entrySetX)之中。當monitorX被其持有線程(這里就是B)釋放時,entrySetX中的一個任意(注意是“任意”,而不一定是Entry Set中等待時間最長或者最短的)線程會被喚醒(即線程的生命周期狀態變更為RUNNABLE)。這個被喚醒的線程會與其他活躍線程(即不處于Entry Set之中,且線程的生命周期狀態為RUNNABLE的線程)再次搶占monitorX。這時,被喚醒的線程如果成功申請到monitorX,那么該線程就從entrySetX中移除。否則,被喚醒的線程仍然會停留在entrySetX,并再次被暫停,以等待下次申請鎖的機會。
如果有個線程執行了objectX.wait(),那么該線程就會被暫停(線程的生命周期狀態會被調整為WAITTING)并被存入objectX的Wait Set(以下記為waitSetX)之中。此時,該線程就被稱為objectX的等待線程。當其他線程執行了objectX.notify()/notifyAll()時,waitSetX中的一個(或者多個,取決于被調用的是notify還是notifyAll方法)任意(注意是“任意”,而不一定是Entry Set中等待時間最長或者最短的)等待線程會被喚醒(線程的生命周期狀態變更為RUNNABLE)。這些被喚醒的線程會與entrySetX中被喚醒的線程以及其他(可能的)活躍線程共同參與搶奪monitorX。如果其中一個被喚醒的等待線程成功申請到鎖,那么該線程就會從waitSetX中移除。否則,這些被喚醒的線程仍然停留在waitSetX中,并再次被暫停,以等待下次申請鎖的機會。
@劉方外
我理解調用對象的 notifyAll方法后,waitSet 上的線程都會加入到 entrySet 中的吧?在一個持有鎖的線程釋放鎖后,應該只有 entrySet 隊列的線程可能獲取鎖,那這個通知是 park 來實現的嗎?是否有保證獲取鎖公平性的相關設置?
1、從Java虛擬機性能的角度來說,Java虛擬機沒有必要在notifyAll調用之后“將Wait Set中的線程移入Entry Set”。首先,從一個“隊列”移動到另外一個“隊列”是有開銷的,其次,雖然notifyAll調用后Wait Set中的多個線程會被喚醒,但是這些被喚醒的線程極端情況下可能沒有任何一個能夠獲得鎖(比如被其他活躍線程搶先下手了)或者即便可以獲得鎖也可能不能繼續運行(比如這些等待線程所需的等待條件又再次不成立)。那么這個時候,這些等待線程仍然需要老老實實在wait set中待著。因此,如果notifyAll調用之后就將等待線程移出wait set會導致浪費(白白地進出“隊列”)。這點可以參考顯式鎖的實現:
java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(Node, int)
/**
* Acquires in exclusive uninterruptible mode for thread already in
* queue. Used by condition wait methods as well as acquire.
*
* @param node the node
* @param arg the acquire argument
* @return {@code true} if interrupted while waiting
*/
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
從上面的代碼可以看出,(使用顯式鎖時)被喚醒的線程獲得鎖(tryAcquire調用返回true)之后才被從wait set中移出(setHead調用)。
2、內部鎖僅僅支持非公平鎖調度。顯式鎖既支持公平鎖又支持非公平鎖。
LockSupport.park/upark是在jdk1.5開始引入的,顯式鎖的在實現線程的暫停和喚醒的時候會用到這個兩個方法。而內部鎖是在jdk1.5之前就已經存在的。
【參考資料】
1、黃文海.Java多線程編程實戰指南(核心篇).電子工業出版社,2017
新人創作打卡挑戰賽發博客就能抽獎!定制產品紅包拿不停!總結
以上是生活随笔為你收集整理的java设置等待锁的时间_java的锁池和等待池的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: @import注解_Spring Boo
- 下一篇: 安卓系统启动