Java多線程下的其他組件之CyclicBarrier、Callable、Future和FutureTask詳解
CyclicBarrier
接著講多線程下的其他組件,第一個(gè)要講的就是CyclicBarrier。CyclicBarrier從字面理解是指循環(huán)屏障,它可以協(xié)同多個(gè)線程,讓多個(gè)線程在這個(gè)屏障前等待,直到所有線程都達(dá)到了這個(gè)屏障時(shí),再一起繼續(xù)執(zhí)行后面的動(dòng)作??匆幌翪yclicBarrier的使用實(shí)例:
public static class CyclicBarrierThread extends Thread { private CyclicBarrier cb; private int sleepSecond; public CyclicBarrierThread(CyclicBarrier cb, int sleepSecond) { this.cb = cb; this.sleepSecond = sleepSecond; } public void run() { try { System.out.println(this.getName() + "運(yùn)行了"); Thread.sleep(sleepSecond * 1000); System.out.println(this.getName() + "準(zhǔn)備等待了, 時(shí)間為" + System.currentTimeMillis()); cb.await(); System.out.println(this.getName() + "結(jié)束等待了, 時(shí)間為" + System.currentTimeMillis()); } catch (Exception e) { e.printStackTrace(); } } } public static void main(String[] args) { Runnable runnable = new Runnable() { public void run() { System.out.println("CyclicBarrier的所有線程await()結(jié)束了,我運(yùn)行了, 時(shí)間為" + System.currentTimeMillis()); } }; CyclicBarrier cb = new CyclicBarrier(3, runnable); CyclicBarrierThread cbt0 = new CyclicBarrierThread(cb, 3); CyclicBarrierThread cbt1 = new CyclicBarrierThread(cb, 6); CyclicBarrierThread cbt2 = new CyclicBarrierThread(cb, 9); cbt0.start(); cbt1.start(); cbt2.start(); }
看一下運(yùn)行結(jié)果:
Thread-0運(yùn)行了
Thread-2運(yùn)行了
Thread-1運(yùn)行了
Thread-0準(zhǔn)備等待了, 時(shí)間為1444650316313
Thread-1準(zhǔn)備等待了, 時(shí)間為1444650319313
Thread-2準(zhǔn)備等待了, 時(shí)間為1444650322313
CyclicBarrier的所有線程await()結(jié)束了,我運(yùn)行了, 時(shí)間為1444650322313
Thread-2結(jié)束等待了, 時(shí)間為1444650322313
Thread-0結(jié)束等待了, 時(shí)間為1444650322313
Thread-1結(jié)束等待了, 時(shí)間為1444650322313
從運(yùn)行結(jié)果看,由于是同一個(gè)CyclicBarrier,Thread-0先運(yùn)行到了await()的地方,等著;Thread-2接著運(yùn)行到了await()的地方,還等著;Thread-1最后運(yùn)行到了await()的地方,所有的線程都運(yùn)行到了await()的地方,所以三個(gè)線程以及指定的Runnable"同時(shí)"運(yùn)行后面的代碼,可以看到,await()之后,四個(gè)線程運(yùn)行的時(shí)間一模一樣,都是1444650322313。
從使用來(lái)看,可能有人覺(jué)得CyclicBarrier和CountDownLatch有點(diǎn)像,都是多個(gè)線程等待相互完成之后,再執(zhí)行后面的代碼。實(shí)際上,CountDownLatch和CyclicBarrier都是用于多個(gè)線程間的協(xié)調(diào)的,它們二者的幾個(gè)差別是:
1、CountDownLatch是在多個(gè)線程都進(jìn)行了latch.countDown()后才會(huì)觸發(fā)事件,喚醒a(bǔ)wait()在latch上的線程,而執(zhí)行countDown()的線程,執(zhí)行完countDown()后會(huì)繼續(xù)自己線程的工作;CyclicBarrier是一個(gè)柵欄,用于同步所有調(diào)用await()方法的線程,線程執(zhí)行了await()方法之后并不會(huì)執(zhí)行之后的代碼,而只有當(dāng)執(zhí)行await()方法的線程數(shù)等于指定的parties之后,這些執(zhí)行了await()方法的線程才會(huì)同時(shí)運(yùn)行
2、CountDownLatch不能循環(huán)使用,計(jì)數(shù)器減為0就減為0了,不能被重置;CyclicBarrier提供了reset()方法,支持循環(huán)使用
3、CountDownLatch當(dāng)調(diào)用countDown()方法的線程數(shù)等于指定的數(shù)量之后,可以喚起多條線程的任務(wù);CyclicBarrier當(dāng)執(zhí)行await()方法的線程等于指定的數(shù)量之后,只能喚起一個(gè)BarrierAction
注意,因?yàn)槭褂肅yclicBarrier的線程都會(huì)阻塞在await方法上,所以在線程池中使用CyclicBarrier時(shí)要特別小心,如果線程池的線程過(guò)少,那么就會(huì)發(fā)生死鎖了
Callable、Future和FutureTask
Callable
Callable和Runnable差不多,兩者都是為那些其實(shí)例可能被另一個(gè)線程執(zhí)行的類(lèi)而設(shè)計(jì)的,最主要的差別在于Runnable不會(huì)返回線程運(yùn)算結(jié)果,Callable可以(假如線程需要返回運(yùn)行結(jié)果)
Future
Future是一個(gè)接口表示異步計(jì)算的結(jié)果,它提供了檢查計(jì)算是否完成的方法,以等待計(jì)算的完成,并獲取計(jì)算的結(jié)果。Future提供了get()、cancel()、isCancel()、isDone()四種方法,表示Future有三種功能:
1、判斷任務(wù)是否完成
2、中斷任務(wù)
3、獲取任務(wù)執(zhí)行結(jié)果
FutureTask
FutureTask是Future的實(shí)現(xiàn)類(lèi),它提供了對(duì)Future的基本實(shí)現(xiàn)。可使用FutureTask包裝Callable或Runnable對(duì)象,因?yàn)镕utureTask實(shí)現(xiàn)了Runnable,所以也可以將FutureTask提交給Executor。
使用方法
Callable、Future、FutureTask一般都是和線程池配合使用的,因?yàn)榫€程池ThreadPoolExecutor的父類(lèi)AbstractExecutorService提供了三種submit方法:
1、public Future<?> subit(Runnable task){...}
2、public <T> Future<T> submit<Runnable task, T result>{...}
3、public <T> Future<T> submit<Callable<T> task>{...}
第2個(gè)用得不多,第1個(gè)和第3個(gè)比較有用
Callable+Future使用示例
public static class CallableThread implements Callable<String> { public String call() throws Exception { System.out.println("進(jìn)入CallableThread的call()方法, 開(kāi)始睡覺(jué), 睡覺(jué)時(shí)間為" + System.currentTimeMillis()); Thread.sleep(10000); return "123"; } } public static void main(String[] args) throws Exception { ExecutorService es = Executors.newCachedThreadPool(); CallableThread ct = new CallableThread(); Future<String> f = es.submit(ct); es.shutdown(); Thread.sleep(5000); System.out.println("主線程等待5秒, 當(dāng)前時(shí)間為" + System.currentTimeMillis()); String str = f.get(); System.out.println("Future已拿到數(shù)據(jù), str = " + str + ", 當(dāng)前時(shí)間為" + System.currentTimeMillis()); }
運(yùn)行結(jié)果為:
進(jìn)入CallableThread的call()方法, 開(kāi)始睡覺(jué), 睡覺(jué)時(shí)間為1444654421368
主線程等待5秒, 當(dāng)前時(shí)間為1444654426369
Future已拿到數(shù)據(jù), str = 123, 當(dāng)前時(shí)間為1444654431369
看到任意一個(gè)利用Callable接口submit上去的任務(wù),只要有一個(gè)Future接受它,F(xiàn)uture便可以在程序任何地點(diǎn)嘗試去獲取這條線程返回出去的數(shù)據(jù),時(shí)間可以比對(duì)一下,正好10000ms,即10s
Callable+FutureTask使用示例
有興趣的可以看下源碼,其實(shí)使用Callable+Future的方式,es.submit(ct)方法返回的Future,底層實(shí)現(xiàn)new出來(lái)的是一個(gè)FutureTask。那么,我們看一下Callable+FutureTask的方式:
public static class CallableThread implements Callable<String> { public String call() throws Exception { System.out.println("進(jìn)入CallableThread的call()方法, 開(kāi)始睡覺(jué), 睡覺(jué)時(shí)間為" + System.currentTimeMillis()); Thread.sleep(10000); return "123"; } } public static void main(String[] args) throws Exception { ExecutorService es = Executors.newCachedThreadPool(); CallableThread ct = new CallableThread(); FutureTask<String> f = new FutureTask<String>(ct); es.submit(f); es.shutdown(); Thread.sleep(5000); System.out.println("主線程等待5秒, 當(dāng)前時(shí)間為" + System.currentTimeMillis()); String str = f.get(); System.out.println("Future已拿到數(shù)據(jù), str = " + str + ", 當(dāng)前時(shí)間為" + System.currentTimeMillis()); }
看下運(yùn)行結(jié)果:
進(jìn)入CallableThread的call()方法, 開(kāi)始睡覺(jué), 睡覺(jué)時(shí)間為1444655049199
主線程等待5秒, 當(dāng)前時(shí)間為1444655054200
Future已拿到數(shù)據(jù), str = 123, 當(dāng)前時(shí)間為1444655059200
和上面的寫(xiě)法運(yùn)行結(jié)果一樣,就不解釋了
使用Callable、Future和FutureTask的好處
上面演示了兩個(gè)例子,其實(shí)反映的是現(xiàn)實(shí)中一種情況,把上面的例子稍微擴(kuò)展一下就是:
有一個(gè)method()方法,方法中執(zhí)行方法A返回一個(gè)數(shù)據(jù)要10秒鐘,A方法后面的代碼一共要執(zhí)行20秒鐘,但是這20秒的代碼中有10秒的方法并不依賴(lài)方法A的執(zhí)行結(jié)果,有10秒鐘的代碼依賴(lài)方法A的執(zhí)行結(jié)果。此時(shí)若采用同步的方式,那么勢(shì)必要先等待10秒鐘,等待方法A執(zhí)行完畢,返回?cái)?shù)據(jù),再執(zhí)行后面20秒的代碼。
不得不說(shuō)這是一種低效率的做法。有了Callable、Future和FutureTask,那么:
1、先把A方法的內(nèi)容放到Callable實(shí)現(xiàn)類(lèi)的call()方法中
2、method()方法中,Callable實(shí)現(xiàn)類(lèi)傳入Executor的submit方法中
3、執(zhí)行后面方法中10秒不依賴(lài)方法A運(yùn)行結(jié)果的代碼
4、獲取方法A的運(yùn)行結(jié)果,執(zhí)行后面方法中10秒依賴(lài)方法A運(yùn)行結(jié)果的代碼
這樣代碼執(zhí)行效率一下子就提高了,程序不必卡在A方法處。
當(dāng)然,也可以不用Callable,采用實(shí)現(xiàn)Runnable的方式,run()方法執(zhí)行完了想個(gè)辦法給method()方法中的某個(gè)變量V賦個(gè)值就好了。但是我上一篇文章開(kāi)頭就說(shuō)了,之所以要用多線程組件,就是因?yàn)镴DK幫我們很好地實(shí)現(xiàn)好了代碼細(xì)節(jié),讓開(kāi)發(fā)者更多可以關(guān)注業(yè)務(wù)層的邏輯。如果使用Runnable的方式,那么我們自己就要考慮很多細(xì)節(jié),比如Runnable實(shí)現(xiàn)類(lèi)的run()方法執(zhí)行完畢給V賦值是否線程安全、10秒后如果A方法沒(méi)有執(zhí)行完導(dǎo)致V還沒(méi)有值怎么辦,何況JDK還給用戶(hù)提供了取消任務(wù)、判斷任務(wù)是否存在等方法。既然JDK已經(jīng)幫我們考慮并實(shí)現(xiàn)這些細(xì)節(jié)了,在沒(méi)有有說(shuō)服力的理由的情況下,我們?yōu)槭裁催€要自己寫(xiě)run()方法的實(shí)現(xiàn)呢?
到此這篇關(guān)于Java多線程下的其他組件之CyclicBarrier、Callable、Future和FutureTask詳解的文章就介紹到這了,更多相關(guān)Java多線程下的其他組件內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
mybatis中使用oracle關(guān)鍵字出錯(cuò)的解決方法
這篇文章主要給大家介紹了關(guān)于mybatis中使用oracle關(guān)鍵字出錯(cuò)的解決方法,文中通過(guò)示例代碼將解決的方法介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起看看吧。2017-08-08SpringBoot快速集成jxls-poi(自定義模板,支持本地文件導(dǎo)出,在線文件導(dǎo)出)
這篇文章主要介紹了SpringBoot快速集成jxls-poi(自定義模板,支持本地文件導(dǎo)出,在線文件導(dǎo)出),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09解決Druid動(dòng)態(tài)數(shù)據(jù)源配置重復(fù)刷錯(cuò)誤日志的問(wèn)題
使用druid數(shù)據(jù)庫(kù)連接池實(shí)現(xiàn)動(dòng)態(tài)的配置數(shù)據(jù)源功能,在配置過(guò)程中出現(xiàn)一個(gè)問(wèn)題既然是用戶(hù)自己配置的數(shù)據(jù)源,就無(wú)法避免輸入錯(cuò)誤,連接失敗等情況,關(guān)于這個(gè)問(wèn)題怎么處理呢,今天小編通過(guò)本文給大家詳細(xì)說(shuō)明下,感興趣的朋友一起看看吧2021-05-05關(guān)于對(duì)Java正則表達(dá)式"\\"的理解
正則表達(dá)式中,\代表轉(zhuǎn)義字符,通常是轉(zhuǎn)義一些特殊字符,下面這篇文章主要給大家介紹了關(guān)于對(duì)Java正則表達(dá)式"\\"的相關(guān)理解,需要的朋友可以參考下2022-09-09Java項(xiàng)目打包發(fā)布到maven私倉(cāng)常見(jiàn)的幾種方式
這篇文章主要介紹了項(xiàng)目打包發(fā)布到maven私倉(cāng)常見(jiàn)的幾種方式,幫助大家更好的理解和學(xué)習(xí)使用Java,感興趣的朋友可以了解下2021-03-03Spring?Security短信驗(yàn)證碼實(shí)現(xiàn)詳解
本文主要介紹了Spring?Security短信驗(yàn)證碼的實(shí)現(xiàn)詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-11-11