天天看點

Java條件對象(Condition)

如果我的線程執行過程中因為沒有滿足一些必要的條件而導緻線程暫停執行怎麼辦?

比如,我們還用銀行賬戶系統做例子,如果有一條線程是從我的賬戶轉出 1000 元到其他賬戶,可是我的賬戶餘額不足 1000 元,那麼怎麼辦?也許你會直接簡單地想到,加上一個 if 條件語句做一下判斷不就可以了,就像這樣:

但是,要注意,千萬不能這樣寫,因為,很有可能會出現這樣的情況:

1. 先執行 if 語句檢查我的賬戶餘額,餘額滿足條件

2. 線程時間片結束被中斷暫停

3. 在這期間執行了一條從我的賬戶取錢的線程,取出錢後餘額就不足了

4. 線程恢複執行,此時餘額不足但是已經執行完畢了 if 語句

由此可見,這樣的代碼藏有緻命的 bug ,那麼,我再來做修改,也許我們可以把鎖對象用上,這樣即使線程暫停也不會受影響了。是的,這樣做确實可以防止其他線程對餘額的操作,可是,這裡面還是有問題:

比如,我的餘額一開始就不夠,這時恰好也有一個存錢的線程進來,如果錢能順利存進來我的餘額就足夠了,可是,我們的鎖對象卻把存錢線程拒之門外,這樣反而不利于線程的順利執行了

鑒于此,我們就需要引入條件對象

通過調用 newCondition 方法可以獲得一個條件對象,而且,應該養成一個給每個條件對象起個好名字的習慣,應該用其所表達的條件為其命名,這樣使人一目了然。在文中的例子中,我們用 sufficientFunds(餘額充足)作為條件對象的名字

如果 transfer 方法發現餘額不足的時候,就會調用:

sufficientFunds.await( );

這時,目前線程就被阻塞(Blocked)了,并且放棄了鎖對象,等待着其他的線程滿足它所需的條件

等待獲得鎖的線程和調用 await 的線程存在本質上的不同,一旦一個線程調用 await 方法,它進入該條件的等待集,當鎖可用時,該線程不能馬上解除阻塞,相反,它處于阻塞狀态,直到另一個線程調用同一條件上的 signalAll 方法為止

在本例中,當我們的條件對象調用 await 方法處于阻塞狀态時,它就在等待一個轉賬存錢的線程來滿足它的條件,是以,我們在寫代碼時,就可以為轉賬存錢的線程最後調用 sufficientFunds.signalAll( ) 方法

這一調用重新激活因為這一條件而等待的所有線程,當這些線程從等待集中移出時,它們再次成為可運作的,排程器将再次激活它們。同時,它們将試圖重新進入該對象。一旦鎖成為可用的,它們中的某個将從 await 調用傳回,獲得該鎖并從被阻塞的地方繼續執行。

是以,當條件對象被重新激活從 await 傳回時,應該再次測試條件,因為即使我的賬戶已經有了收入,條件還不一定被滿足

我們應該将 await 調用放入循環體中

我們還應該注意的是,當一個線程調用 await 後,它無法激活自身,隻能依靠等待其他的線程來滿足它的條件才能繼續執行,如果沒有其他線程滿足它的條件,它将永遠無法繼續執行,這就是 死鎖 現象

那麼,應該在什麼時候調用 signalAll 方法呢?應該在每次對象狀态有利于等待線程的方向改變時調用。也就是本例中,一個賬戶餘額發生改變時調用

綜上所述,最終的 transfer 方法應該寫成這樣:

最後還要注意,Java 中有 signal 和 signalAll 兩種方法,signal 是随機解除一個等待集中的線程的阻塞狀态,signalAll 是解除所有等待集中的線程的阻塞狀态。signal 方法的效率會比 signalAll 高,但是它存在危險,因為它一次隻解除一個線程的阻塞狀态,是以,如果等待集中有多個線程都滿足了條件,也隻能喚醒一個,其他的線程可能會導緻死鎖

下一篇: 中斷線程