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