饑餓發(fā)生的原因:
- 高優(yōu)先級的線程占用了大部分的cpu時間漓糙,低優(yōu)先級線程發(fā)生饑餓
- 線程被永久堵塞在一個等待進入同步塊的狀態(tài)
- 線程在等待一個本身(在其上調用wait())也處于永久等待完成的對象
java中實現(xiàn)公平鎖
- 使用鎖而不是同步塊
- 公平鎖
如果一個線程的cpu執(zhí)行時間都被其他線程搶占了峡懈,導致得不到cpu執(zhí)行膏燕,這種情況就叫做“饑餓”填大,這個線程就會出現(xiàn)饑餓致死的現(xiàn)象,因為永遠無法得到cpu的執(zhí)行。解決饑餓現(xiàn)象的方法就是實現(xiàn)公平誓篱,保證所有線程都公平的獲得執(zhí)行的機會嗦明。
java中發(fā)生線程饑餓的原因
- 高優(yōu)先級的線程占用了大部分的cpu時間笼沥,低優(yōu)先級線程發(fā)生饑餓
- 線程被永久堵塞在一個等待進入同步塊的狀態(tài)
- 線程在等待一個本身(在其上調用wait())也處于永久等待完成的對象
高優(yōu)先級的線程占用了大部分的cpu時間,低優(yōu)先級線程發(fā)生饑餓
你可以給每個線程單獨的設置優(yōu)先級娶牌。優(yōu)先級越高奔浅,就會獲得越高的cpu執(zhí)行的機會。
線程被永久堵塞在一個等待進入同步塊的狀態(tài)
java 的synchronize語句塊不保證線程進入語句塊的順序诗良,所以這就存在一個可能的問題汹桦,有一個線程一直阻塞在synchronize語句塊,永遠都無法進入synchronize鉴裹。
線程在等待一個本身(在其上調用wait())也處于永久等待完成的對象
同樣的舞骆,類似synchronize,notify也不保證線程被喚醒的順序径荔。所以也存在一個風險督禽,就是一個wait的線程一直處于wait的狀態(tài),永遠也沒有被notify所喚醒猖凛。
java中實現(xiàn)公平鎖
雖然無法實現(xiàn)完全100%公平赂蠢,但是我們仍然可以盡可能的提高線程的公平性。
首先辨泳,我們研究一個簡單的synchronize語句塊:
public class Synchronizer{
public synchronized void doSynchronized(){
//do a lot of work which takes a long time
}
}
如果超過一個線程調用這個方法虱岂,那么只有一個線程可以進入這個方法執(zhí)行玖院,其他線程都必須等待,直到該線程退出第岖。而且我們無法知道下一個進入synchronize的語句塊的線程會是那一個
使用lock而不是synchronize
為了增加等待線程的公平性难菌,我們可以用lock來替換synchronize
public class Synchronizer{
Lock lock = new Lock();
public void doSynchronized() throws InterruptedException{
this.lock.lock();
//critical section, do a lot of work which takes a long time
this.lock.unlock();
}
}
lock類的一個簡單的實現(xiàn)如下:
public class Lock{
private boolean isLocked = false;
private Thread lockingThread = null;
public synchronized void lock() throws InterruptedException{
while(isLocked){
wait();
}
isLocked = true;
lockingThread = Thread.currentThread();
}
public synchronized void unlock(){
if(this.lockingThread != Thread.currentThread()){
throw new IllegalMonitorStateException(
"Calling thread has not locked this lock");
}
isLocked = false;
lockingThread = null;
notify();
}
}
注意到上面對Lock的實現(xiàn),如果存在多線程并發(fā)訪問lock()蔑滓,這些線程將阻塞在對lock()方法的訪問上郊酒。另外,如果鎖已經(jīng)鎖上(校對注:這里指的是isLocked等于true時)键袱,這些線程將阻塞在while(isLocked)循環(huán)的wait()調用里面燎窘。要記住的是,當線程正在等待進入lock() 時蹄咖,可以調用wait()釋放其鎖實例對應的同步鎖褐健,使得其他多個線程可以進入lock()方法,并調用wait()方法澜汤。
這回看下doSynchronized()蚜迅,你會注意到在lock()和unlock()之間的注釋:在這兩個調用之間的代碼將運行很長一段時間。進一步設想俊抵,這段代碼將長時間運行谁不,和進入lock()并調用wait()來比較的話。這意味著大部分時間用在等待進入鎖和進入臨界區(qū)的過程是用在wait()的等待中徽诲,而不是被阻塞在試圖進入lock()方法中刹帕。
在早些時候提到過,同步塊不會對等待進入的多個線程誰能獲得訪問做任何保障馏段,同樣當調用notify()時轩拨,wait()也不會做保障一定能喚醒線程因此這個版本的Lock類和doSynchronized()那個版本就保障公平性而言,沒有任何區(qū)別院喜。
但我們能改變這種情況亡蓉。當前的Lock類版本調用自己的wait()方法,** 如果每個線程在不同的對象上調用wait()喷舀,那么只有一個線程會在該對象上調用wait()砍濒,Lock類可以決定哪個對象能對其調用notify(),因此能做到有效的選擇喚醒哪個線程硫麻。**
實際上這就是公平鎖的實現(xiàn)思想
公平鎖
下面來講述將上面Lock類轉變?yōu)楣芥iFairLock爸邢。你會注意到新的實現(xiàn)和之前的Lock類中的同步和wait()/notify()稍有不同。
準確地說如何從之前的Lock類做到公平鎖的設計是一個漸進設計的過程拿愧,每一步都是在解決上一步的問題而前進的:Nested Monitor Lockout, Slipped Conditions和Missed Signals杠河。這些本身的討論雖已超出本文的范圍,但其中每一步的內容都將會專題進行討論。重要的是券敌,每一個調用lock()的線程都會進入一個隊列唾戚,當解鎖后,只有隊列里的第一個線程被允許鎖住Farlock實例待诅,所有其它的線程都將處于等待狀態(tài)叹坦,直到他們處于隊列頭部。
public class FairLock {
private boolean isLocked = false;
private Thread lockingThread = null;
private List<QueueObject> waitingThreads =
new ArrayList<QueueObject>();
public void lock() throws InterruptedException{
QueueObject queueObject = new QueueObject();
boolean isLockedForThisThread = true;
synchronized(this){
waitingThreads.add(queueObject);
}
while(isLockedForThisThread){
synchronized(this){
isLockedForThisThread =
isLocked || waitingThreads.get(0) != queueObject;
if(!isLockedForThisThread){
isLocked = true;
waitingThreads.remove(queueObject);
lockingThread = Thread.currentThread();
return;
}
}
try{
queueObject.doWait();
}catch(InterruptedException e){
synchronized(this) { waitingThreads.remove(queueObject); }
throw e;
}
}
}
public synchronized void unlock(){
if(this.lockingThread != Thread.currentThread()){
throw new IllegalMonitorStateException(
"Calling thread has not locked this lock");
}
isLocked = false;
lockingThread = null;
if(waitingThreads.size() > 0){
waitingThreads.get(0).doNotify();
}
}
}
public class QueueObject {
private boolean isNotified = false;
public synchronized void doWait() throws InterruptedException {
while(!isNotified){
this.wait();
}
this.isNotified = false;
}
public synchronized void doNotify() {
this.isNotified = true;
this.notify();
}
public boolean equals(Object o) {
return this == o;
}
}
首先注意到lock()方法不在聲明為synchronized卑雁,取而代之的是對必需同步的代碼募书,在synchronized中進行嵌套。
FairLock新創(chuàng)建了一個QueueObject的實例测蹲,并對每個調用lock()的線程進行入隊列莹捡。調用unlock()的線程將從隊列頭部獲取QueueObject,并對其調用doNotify()扣甲,以喚醒在該對象上等待的線程道盏。通過這種方式,在同一時間僅有一個等待線程獲得喚醒文捶,而不是所有的等待線程。這也是實現(xiàn)FairLock公平性的核心所在媒咳。
請注意粹排,在同一個同步塊中,鎖狀態(tài)依然被檢查和設置涩澡,以避免出現(xiàn)滑漏條件启妹。
還需注意到鸥昏,QueueObject實際是一個semaphore。doWait()和doNotify()方法在QueueObject中保存著信號。這樣做以避免一個線程在調用queueObject.doWait()之前被另一個調用unlock()并隨之調用queueObject.doNotify()的線程重入测柠,從而導致信號丟失。queueObject.doWait()調用放置在synchronized(this)塊之外漆撞,以避免被monitor嵌套鎖死祟剔,所以另外的線程可以解鎖,只要當沒有線程在lock方法的synchronized(this)塊中執(zhí)行即可芒涡。
最后柴灯,注意到queueObject.doWait()在try – catch塊中是怎樣調用的。在InterruptedException拋出的情況下费尽,線程得以離開lock()赠群,并需讓它從隊列中移除。
性能考慮
如果比較Lock和FairLock類旱幼,你會注意到在FairLock類中l(wèi)ock()和unlock()還有更多需要深入的地方查描。這些額外的代碼會導致FairLock的同步機制實現(xiàn)比Lock要稍微慢些。究竟存在多少影響,還依賴于應用在FairLock臨界區(qū)執(zhí)行的時長冬三。執(zhí)行時長越大匀油,F(xiàn)airLock帶來的負擔影響就越小,當然這也和代碼執(zhí)行的頻繁度相關长豁。