Java并發(fā)之“饑餓”和“公平鎖”(Starvation and Fairness)

饑餓發(fā)生的原因:

  • 高優(yōu)先級的線程占用了大部分的cpu時間,低優(yōu)先級線程發(fā)生饑餓
  • 線程被永久堵塞在一個等待進入同步塊的狀態(tài)
  • 線程在等待一個本身(在其上調(diào)用wait())也處于永久等待完成的對象

java中實現(xiàn)公平鎖

  • 使用鎖而不是同步塊
  • 公平鎖

如果一個線程的cpu執(zhí)行時間都被其他線程搶占了,導(dǎo)致得不到cpu執(zhí)行,這種情況就叫做“饑餓”,這個線程就會出現(xiàn)饑餓致死的現(xiàn)象,因為永遠無法得到cpu的執(zhí)行。解決饑餓現(xiàn)象的方法就是實現(xiàn)公平,保證所有線程都公平的獲得執(zhí)行的機會。

java中發(fā)生線程饑餓的原因

  • 高優(yōu)先級的線程占用了大部分的cpu時間,低優(yōu)先級線程發(fā)生饑餓
  • 線程被永久堵塞在一個等待進入同步塊的狀態(tài)
  • 線程在等待一個本身(在其上調(diào)用wait())也處于永久等待完成的對象

高優(yōu)先級的線程占用了大部分的cpu時間,低優(yōu)先級線程發(fā)生饑餓

你可以給每個線程單獨的設(shè)置優(yōu)先級。優(yōu)先級越高,就會獲得越高的cpu執(zhí)行的機會。

線程被永久堵塞在一個等待進入同步塊的狀態(tài)

java 的synchronize語句塊不保證線程進入語句塊的順序,所以這就存在一個可能的問題,有一個線程一直阻塞在synchronize語句塊,永遠都無法進入synchronize。

線程在等待一個本身(在其上調(diào)用wait())也處于永久等待完成的對象

同樣的,類似synchronize,notify也不保證線程被喚醒的順序。所以也存在一個風(fēng)險,就是一個wait的線程一直處于wait的狀態(tài),永遠也沒有被notify所喚醒。

java中實現(xiàn)公平鎖

雖然無法實現(xiàn)完全100%公平,但是我們?nèi)匀豢梢员M可能的提高線程的公平性。
首先,我們研究一個簡單的synchronize語句塊:

public class Synchronizer{

  public synchronized void doSynchronized(){
    //do a lot of work which takes a long time
  }

}

如果超過一個線程調(diào)用這個方法,那么只有一個線程可以進入這個方法執(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()調(diào)用里面。要記住的是,當(dāng)線程正在等待進入lock() 時,可以調(diào)用wait()釋放其鎖實例對應(yīng)的同步鎖,使得其他多個線程可以進入lock()方法,并調(diào)用wait()方法。

這回看下doSynchronized(),你會注意到在lock()和unlock()之間的注釋:在這兩個調(diào)用之間的代碼將運行很長一段時間。進一步設(shè)想,這段代碼將長時間運行,和進入lock()并調(diào)用wait()來比較的話。這意味著大部分時間用在等待進入鎖和進入臨界區(qū)的過程是用在wait()的等待中,而不是被阻塞在試圖進入lock()方法中。

在早些時候提到過,同步塊不會對等待進入的多個線程誰能獲得訪問做任何保障,同樣當(dāng)調(diào)用notify()時,wait()也不會做保障一定能喚醒線程因此這個版本的Lock類和doSynchronized()那個版本就保障公平性而言,沒有任何區(qū)別。

但我們能改變這種情況。當(dāng)前的Lock類版本調(diào)用自己的wait()方法,** 如果每個線程在不同的對象上調(diào)用wait(),那么只有一個線程會在該對象上調(diào)用wait(),Lock類可以決定哪個對象能對其調(diào)用notify(),因此能做到有效的選擇喚醒哪個線程。**
實際上這就是公平鎖的實現(xiàn)思想

公平鎖

下面來講述將上面Lock類轉(zhuǎn)變?yōu)楣芥iFairLock。你會注意到新的實現(xiàn)和之前的Lock類中的同步和wait()/notify()稍有不同。

準(zhǔn)確地說如何從之前的Lock類做到公平鎖的設(shè)計是一個漸進設(shè)計的過程,每一步都是在解決上一步的問題而前進的:Nested Monitor Lockout, Slipped Conditions和Missed Signals。這些本身的討論雖已超出本文的范圍,但其中每一步的內(nèi)容都將會專題進行討論。重要的是,每一個調(diào)用lock()的線程都會進入一個隊列,當(dāng)解鎖后,只有隊列里的第一個線程被允許鎖住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的實例,并對每個調(diào)用lock()的線程進行入隊列。調(diào)用unlock()的線程將從隊列頭部獲取QueueObject,并對其調(diào)用doNotify(),以喚醒在該對象上等待的線程。通過這種方式,在同一時間僅有一個等待線程獲得喚醒,而不是所有的等待線程。這也是實現(xiàn)FairLock公平性的核心所在。

請注意,在同一個同步塊中,鎖狀態(tài)依然被檢查和設(shè)置,以避免出現(xiàn)滑漏條件。

還需注意到,QueueObject實際是一個semaphore。doWait()和doNotify()方法在QueueObject中保存著信號。這樣做以避免一個線程在調(diào)用queueObject.doWait()之前被另一個調(diào)用unlock()并隨之調(diào)用queueObject.doNotify()的線程重入,從而導(dǎo)致信號丟失。queueObject.doWait()調(diào)用放置在synchronized(this)塊之外,以避免被monitor嵌套鎖死,所以另外的線程可以解鎖,只要當(dāng)沒有線程在lock方法的synchronized(this)塊中執(zhí)行即可。

最后,注意到queueObject.doWait()在try – catch塊中是怎樣調(diào)用的。在InterruptedException拋出的情況下,線程得以離開lock(),并需讓它從隊列中移除。

性能考慮

如果比較Lock和FairLock類,你會注意到在FairLock類中l(wèi)ock()和unlock()還有更多需要深入的地方。這些額外的代碼會導(dǎo)致FairLock的同步機制實現(xiàn)比Lock要稍微慢些。究竟存在多少影響,還依賴于應(yīng)用在FairLock臨界區(qū)執(zhí)行的時長。執(zhí)行時長越大,F(xiàn)airLock帶來的負(fù)擔(dān)影響就越小,當(dāng)然這也和代碼執(zhí)行的頻繁度相關(guān)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容