并发编程实践三:Condition
Condition實例始終被綁定到一個鎖(Lock)上。Lock替代了Java的synchronized方法,而Condition則替代了Object的監視器方法,包含wait、notify和notifyAll(想很多其它的了解能夠看我的博客:Java并發編程3-等待、通知和中斷)。而在Condition中相應為await、signal和signalAll。這篇文章主要講述Condition的用法。以及它的實現機制。
Condition的使用
與Object的監視器方法不同。每一個Lock能夠相應多個Condition對象,這樣等待的線程就能夠分散到多個等待集合中。就能夠針對不同的等待集合來依次喚醒線程。實現喚醒效率的提高(不再須要喚醒全部線程)。看以下的樣例:
public class BoundedBuffer {final Lock lock = new ReentrantLock();final Condition notFull = lock.newCondition();final Condition notEmpty = lock.newCondition();final Object[] items = new Object[100];int putptr, takeptr, count;public void put(Object x) throws InterruptedException {lock.lock();try {while (count == items.length)notFull.await();items[putptr] = x;if (++putptr == items.length)putptr = 0;++count;notEmpty.signal(); //喚醒一個take線程} finally {lock.unlock();}}public Object take() throws InterruptedException {lock.lock();try {while (count == 0)notEmpty.await();Object x = items[takeptr];if (++takeptr == items.length)takeptr = 0;--count;notFull.signal(); //喚醒一個put線程return x;} finally {lock.unlock();}} }
以下我們來看看Condition的主要方法:
await
造成當前線程在接到信號或被中斷之前一直處于等待狀態。
與此Condition相關的鎖以原子方式釋放,而且出于線程調度的目的,將禁用當前線程,且在發生下面四種情況之中的一個曾經,當前線程將一直處于休眠狀態:
?1)其它某個線程調用此Condition的signal()方法,而且碰巧將當前線程選為被喚醒的線程。或者
?2)其它某個線程調用此Condition的signalAll()方法。或者
?3)其它某個線程中斷當前線程,且支持中斷線程的掛起;或者
?4)已超過指定的等待時間。或者
?5)發生“虛假喚醒”。
在全部情況下。在此方法返回到當前線程前。都必須又一次獲取與此條件有關的鎖。
await支持無參數版本號(一直等待)、帶時間參數的版本號(僅僅等待指定時間或等待至某個時間)和支持不可中斷的等待。
signal
喚醒一個等待線程。
假設全部的線程都在等待此條件。則選擇當中的一個喚醒。在從 await 返回之前,該線程必須又一次獲取鎖。
signalAll
喚醒全部等待線程。
假設全部的線程都在等待此條件,則喚醒全部線程。在從 await 返回之前,每一個線程都必須又一次獲取鎖。
在使用Condition時,須要注意的是Condition的實例本身也是一個Object,也帶有wait、notify和notifyAll方法,注意不要搞混。
Condition的實現
AbstractQueuedLongSynchronizer.ConditionObject是Condition的詳細實現類,使用了一個FIFO隊列來保存等待的線程,await將一個線程放入等待隊列中,signal每次喚醒等待時間最長的線程(而notify則是隨意喚醒一個線程)。signalAll則喚醒全部等待線程。等待隊列的節點使用和AQS的隊列同樣的節點(見上一篇:“并發編程實踐二:AbstractQueuedSynchronizer”),隊列的head和tail的定義例如以下:
public class ConditionObject implements Condition, java.io.Serializable {private transient Node firstWaiter;private transient Node lastWaiter;。。。。。。 }
?
和AQS不同的是,ConditionObject使用nextWaiter指向下一個節點(AQS中使用prev和next),而且waitStatus屬性值為Node.CONDITION。
當一個線程獲取了鎖后,它能夠調用該鎖相應的Condition的await方法將自己堵塞:
?1)假設當前線程被中斷,則拋出中斷異常;
?2)將當前線程放置到Condition的等待隊列中;
?3)釋放當前線程的鎖,而且保存鎖定狀態;
?4)在收到信號、中斷或超時前,一直堵塞;
?5)使用保存的鎖定狀態又一次獲取鎖;
?6)假設步驟4的堵塞過程中發生中斷,則拋出中斷異常。
?
整個過程并不復雜,須要注意的是堵塞須要放在一個循環中。防止“虛假喚醒”,之所以要保存鎖定狀態,是為了使用排它模式來獲取鎖。
線程能夠調用signal來將當前Condition的等待隊列中的第一個節點移動到擁有鎖的等待隊列:
?1)假設不是排它模式。則拋出IllegalMonitorStateException異常。
?2)將等待隊列的第一個節點出隊列,并將其增加AQS的鎖隊列。
將因為signal總是從隊列的第一個節點開始處理。因此總是能夠保持喚醒的次序。
signal一開始就運行isHeldExclusively推斷是否為排它模式,在ReentrantLock中的實現例如以下:
也就是當當前線程為鎖的擁有者時。才繼續運行。而在transferForSignal中,假設節點的waitStatus不是CONDITION,那么就僅僅會是CANCELLED(在await操作中運行fullyRelease時。假設失敗會將節點的waitStatus設置到CANCELLED);enq將節點增加AQS的堵塞隊列,返回節點的前續節點,當前續節點被取消(ws > 0),或者更改狀態失敗(這里同意失敗,失敗后被喚醒的線程在acquireQueued中會再次設置前續節點的狀態,直到成功)后,將運行喚醒線程的操作。
線程也能夠調用signalAll將全部線程從此Condition的等待隊列移動到擁有鎖的等待隊列。
public final void signalAll() {if (!isHeldExclusively())throw new IllegalMonitorStateException();Node first = firstWaiter;if (first != null)doSignalAll(first); } private void doSignalAll(Node first) {lastWaiter = firstWaiter = null;do {Node next = first.nextWaiter;first.nextWaiter = null;transferForSignal(first);first = next;} while (first != null); }
signalAll在doSignalAll中依次調用transferForSignal將Condition的等待隊列中的全部節點移動到鎖的等待隊列中。
結束語
Condition在設計時就充分考慮了Object的監視器方法的缺陷。一個lock能夠相應多個Condition,從而能夠使線程分散到多個等待隊列中,使應用更為靈活,而且在實現上使用了FIFO隊列來保存等待線程,確保了能夠做到使用signal按FIFO方式喚醒等待線程。避免每次喚醒全部線程導致數據競爭。
Condition這種設計相同也導致使用上要比Object的監視器方法更為復雜,你須要考慮使用多少個Condition。在什么地方使用哪個condition等等?因為Condition是和Lock配合使用的。所以是否使用Condition須要和Lock一起綜合考慮。
轉載于:https://www.cnblogs.com/mengfanrong/p/5109351.html
總結
以上是生活随笔為你收集整理的并发编程实践三:Condition的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: P3966 [TJOI2013]单词
- 下一篇: 字典的遍历