Java并發(fā)之Condition案例詳解
在使用Lock之前,我們使用的最多的同步方式應(yīng)該是synchronized關(guān)鍵字來(lái)實(shí)現(xiàn)同步方式了。配合Object的wait()、notify()系列方法可以實(shí)現(xiàn)等待/通知模式。Condition接口也提供了類似Object的監(jiān)視器方法,與Lock配合可以實(shí)現(xiàn)等待/通知模式,但是這兩者在使用方式以及功能特性上還是有差別的。Object和Condition接口的一些對(duì)比。摘自《Java并發(fā)編程的藝術(shù)》
一、Condition接口介紹和示例
首先我們需要明白condition對(duì)象是依賴于lock對(duì)象的,意思就是說(shuō)condition對(duì)象需要通過(guò)lock對(duì)象進(jìn)行創(chuàng)建出來(lái)(調(diào)用Lock對(duì)象的newCondition()方法)。consition的使用方式非常的簡(jiǎn)單。但是需要注意在調(diào)用方法前獲取鎖。
package com.ydl.test.juc; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class ConditionUseCase { public Lock lock = new ReentrantLock(); public Condition condition = lock.newCondition(); public static void main(String[] args) { ConditionUseCase useCase = new ConditionUseCase(); ExecutorService executorService = Executors.newFixedThreadPool (2); executorService.execute(new Runnable() { @Override public void run() { useCase.conditionWait(); } }); executorService.execute(new Runnable() { @Override public void run() { useCase.conditionSignal(); } }); } public void conditionWait() { lock.lock(); try { System.out.println(Thread.currentThread().getName() + "拿到鎖了"); System.out.println(Thread.currentThread().getName() + "等待信號(hào)"); condition.await(); System.out.println(Thread.currentThread().getName() + "拿到信號(hào)"); }catch (Exception e){ }finally { lock.unlock(); } } public void conditionSignal() { lock.lock(); try { Thread.sleep(5000); System.out.println(Thread.currentThread().getName() + "拿到鎖了"); condition.signal(); System.out.println(Thread.currentThread().getName() + "發(fā)出信號(hào)"); }catch (Exception e){ }finally { lock.unlock(); } } }
1 pool-1-thread-1拿到鎖了
2 pool-1-thread-1等待信號(hào)
3 pool-1-thread-2拿到鎖了
4 pool-1-thread-2發(fā)出信號(hào)
如示例所示,一般都會(huì)將Condition對(duì)象作為成員變量。當(dāng)調(diào)用await()方法后,當(dāng)前線程會(huì)釋放鎖并在此等待,而其他線程調(diào)用Condition對(duì)象的signal()方法,通知當(dāng)前線程后,當(dāng)前線程才從await()方法返回,并且在返回前已經(jīng)獲取了鎖。
二、Condition接口常用方法
condition可以通俗的理解為條件隊(duì)列。當(dāng)一個(gè)線程在調(diào)用了await方法以后,直到線程等待的某個(gè)條件為真的時(shí)候才會(huì)被喚醒。這種方式為線程提供了更加簡(jiǎn)單的等待/通知模式。Condition必須要配合鎖一起使用,因?yàn)閷?duì)共享狀態(tài)變量的訪問(wèn)發(fā)生在多線程環(huán)境下。一個(gè)Condition的實(shí)例必須與一個(gè)Lock綁定,因此Condition一般都是作為L(zhǎng)ock的內(nèi)部實(shí)現(xiàn)。
- await() :造成當(dāng)前線程在接到信號(hào)或被中斷之前一直處于等待狀態(tài)。
- await(long time, TimeUnit unit) :造成當(dāng)前線程在接到信號(hào)、被中斷或到達(dá)指定等待時(shí)間之前一直處于等待狀態(tài)。
- awaitNanos(long nanosTimeout) :造成當(dāng)前線程在接到信號(hào)、被中斷或到達(dá)指定等待時(shí)間之前一直處于等待狀態(tài)。返回值表示剩余時(shí)間,如果在nanosTimesout之前喚醒,那么返回值 = nanosTimeout - 消耗時(shí)間,如果返回值 <= 0 ,則可以認(rèn)定它已經(jīng)超時(shí)了。
- awaitUninterruptibly() :造成當(dāng)前線程在接到信號(hào)之前一直處于等待狀態(tài)?!咀⒁猓涸摲椒▽?duì)中斷不敏感】。
- awaitUntil(Date deadline) :造成當(dāng)前線程在接到信號(hào)、被中斷或到達(dá)指定最后期限之前一直處于等待狀態(tài)。如果沒(méi)有到指定時(shí)間就被通知,則返回true,否則表示到了指定時(shí)間,返回返回false。
- signal() :?jiǎn)拘岩粋€(gè)等待線程。該線程從等待方法返回前必須獲得與Condition相關(guān)的鎖。
- signal()All :?jiǎn)拘阉械却€程。能夠從等待方法返回的線程必須獲得與Condition相關(guān)的鎖。
三、Condition接口原理簡(jiǎn)單解析
Condition是AQS的內(nèi)部類。每個(gè)Condition對(duì)象都包含一個(gè)隊(duì)列(等待隊(duì)列)。等待隊(duì)列是一個(gè)FIFO的隊(duì)列,在隊(duì)列中的每個(gè)節(jié)點(diǎn)都包含了一個(gè)線程引用,該線程就是在Condition對(duì)象上等待的線程,如果一個(gè)線程調(diào)用了Condition.await()方法,那么該線程將會(huì)釋放鎖、構(gòu)造成節(jié)點(diǎn)加入等待隊(duì)列并進(jìn)入等待狀態(tài)。等待隊(duì)列的基本結(jié)構(gòu)如下所示。
等待分為首節(jié)點(diǎn)和尾節(jié)點(diǎn)。當(dāng)一個(gè)線程調(diào)用Condition.await()方法,將會(huì)以當(dāng)前線程構(gòu)造節(jié)點(diǎn),并將節(jié)點(diǎn)從尾部加入等待隊(duì)列。新增節(jié)點(diǎn)就是將尾部節(jié)點(diǎn)指向新增的節(jié)點(diǎn)。節(jié)點(diǎn)引用更新本來(lái)就是在獲取鎖以后的操作,所以不需要CAS保證。同時(shí)也是線程安全的操作。
3.1、等待
當(dāng)線程調(diào)用了await方法以后。線程就作為隊(duì)列中的一個(gè)節(jié)點(diǎn)被加入到等待隊(duì)列中去了。同時(shí)會(huì)釋放鎖的擁有。當(dāng)從await方法返回的時(shí)候。一定會(huì)獲取condition相關(guān)聯(lián)的鎖。當(dāng)?shù)却?duì)列中的節(jié)點(diǎn)被喚醒的時(shí)候,則喚醒節(jié)點(diǎn)的線程開始嘗試獲取同步狀態(tài)。如果不是通過(guò) 其他線程調(diào)用Condition.signal()方法喚醒,而是對(duì)等待線程進(jìn)行中斷,則會(huì)拋出InterruptedException異常信息。
3.2、通知
調(diào)用Condition的signal()方法,將會(huì)喚醒在等待隊(duì)列中等待最長(zhǎng)時(shí)間的節(jié)點(diǎn)(條件隊(duì)列里的首節(jié)點(diǎn)),在喚醒節(jié)點(diǎn)前,會(huì)將節(jié)點(diǎn)移到同步隊(duì)列中。當(dāng)前線程加入到等待隊(duì)列中如圖所示:
在調(diào)用signal()方法之前必須先判斷是否獲取到了鎖。接著獲取等待隊(duì)列的首節(jié)點(diǎn),將其移動(dòng)到同步隊(duì)列并且利用LockSupport喚醒節(jié)點(diǎn)中的線程。節(jié)點(diǎn)從等待隊(duì)列移動(dòng)到同步隊(duì)列如下圖所示:
被喚醒的線程將從await方法中的while循環(huán)中退出。隨后加入到同步狀態(tài)的競(jìng)爭(zhēng)當(dāng)中去。成功獲取到競(jìng)爭(zhēng)的線程則會(huì)返回到await方法之前的狀態(tài)。
四、總結(jié)
調(diào)用await方法后,將當(dāng)前線程加入Condition等待隊(duì)列中。當(dāng)前線程釋放鎖。否則別的線程就無(wú)法拿到鎖而發(fā)生死鎖。自旋(while)掛起,不斷檢測(cè)節(jié)點(diǎn)是否在同步隊(duì)列中了,如果是則嘗試獲取鎖,否則掛起。當(dāng)線程被signal方法喚醒,被喚醒的線程將從await()方法中的while循環(huán)中退出來(lái),然后調(diào)用acquireQueued()方法競(jìng)爭(zhēng)同步狀態(tài)。
五、利用Condition實(shí)現(xiàn)生產(chǎn)者消費(fèi)者模式
import java.util.LinkedList; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class BoundedQueue { private LinkedList<Object> buffer; //生產(chǎn)者容器 private int maxSize ; //容器最大值是多少 private Lock lock; private Condition fullCondition; private Condition notFullCondition; BoundedQueue(int maxSize){ this.maxSize = maxSize; buffer = new LinkedList<Object>(); lock = new ReentrantLock(); fullCondition = lock.newCondition(); notFullCondition = lock.newCondition(); } /** * 生產(chǎn)者 * @param obj * @throws InterruptedException */ public void put(Object obj) throws InterruptedException { lock.lock(); //獲取鎖 try { while (maxSize == buffer.size()){ notFullCondition.await(); //滿了,添加的線程進(jìn)入等待狀態(tài) } buffer.add(obj); fullCondition.signal(); //通知 } finally { lock.unlock(); } } /** * 消費(fèi)者 * @return * @throws InterruptedException */ public Object get() throws InterruptedException { Object obj; lock.lock(); try { while (buffer.size() == 0){ //隊(duì)列中沒(méi)有數(shù)據(jù)了 線程進(jìn)入等待狀態(tài) fullCondition.await(); } obj = buffer.poll(); notFullCondition.signal(); //通知 } finally { lock.unlock(); } return obj; } }
到此這篇關(guān)于Java并發(fā)之Condition案例詳解的文章就介紹到這了,更多相關(guān)Java并發(fā)之Condition案例內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringMvc3+extjs4實(shí)現(xiàn)上傳與下載功能
這篇文章主要為大家詳細(xì)介紹了SpringMvc3+extjs4實(shí)現(xiàn)上傳與下載功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06springboot單文件下載和多文件壓縮zip下載的實(shí)現(xiàn)
這篇文章主要介紹了springboot單文件下載和多文件壓縮zip下載的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11Java中Singleton的3種實(shí)現(xiàn)方式詳解
這篇文章主要給大家介紹了關(guān)于Java中Singleton的3種實(shí)現(xiàn)方式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02解決@PathVariable出現(xiàn)點(diǎn)號(hào).時(shí)導(dǎo)致路徑參數(shù)截?cái)喃@取不全的問(wèn)題
這篇文章主要介紹了解決@PathVariable出現(xiàn)點(diǎn)號(hào).時(shí)導(dǎo)致路徑參數(shù)截?cái)喃@取不全的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08SpringMVC解析JSON請(qǐng)求數(shù)據(jù)問(wèn)題解析
這篇文章主要介紹了SpringMVC解析JSON請(qǐng)求數(shù)據(jù)問(wèn)題解析,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-04-04