Java并發(fā)編程(七):線程池

一宝踪、為什么要用線程池遍烦?

Java 中的線程池是運(yùn)用場(chǎng)景最多的并發(fā)框架,幾乎所有需要異步或并發(fā)執(zhí)行任務(wù)的程序都可以使用線程池宰啦。在開發(fā)過程中,合理地使用線程池能夠帶來(lái) 3個(gè)好處:

1)降低資源消耗饼拍。通過重復(fù)利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗赡模。

2)提高響應(yīng)速度。當(dāng)任務(wù)到達(dá)時(shí)师抄,任務(wù)可以不需要等到線程創(chuàng)建就能立即執(zhí)行纺裁。假設(shè)一個(gè)服務(wù)器完成一項(xiàng)任務(wù)所需時(shí)間為:T1 創(chuàng)建線程時(shí)間,T2 在線程中執(zhí)行任務(wù)的時(shí)間司澎,T3 銷毀線程時(shí)間。 如果:T1 + T3 遠(yuǎn)大于 T2栋豫,則可以采用線程池挤安,以提高服務(wù)器性能。線程池技術(shù)正是關(guān)注如何縮短或調(diào)整 T1,T3 時(shí)間的技術(shù)丧鸯,從而提高服務(wù)器程序性能的蛤铜。它把 T1,T3 分別安排在服務(wù)器程序的啟動(dòng)和結(jié)束的時(shí)間段或者一些空閑的時(shí)間段丛肢,這樣在服務(wù)器程序處理客戶請(qǐng)求時(shí)围肥,不會(huì)有 T1,T3 的開銷了蜂怎。

3)提高線程的可管理性穆刻。線程是稀缺資源,如果無(wú)限制地創(chuàng)建杠步,不僅會(huì)消耗系統(tǒng)資源氢伟,還會(huì)降低系統(tǒng)的穩(wěn)定性榜轿,使用線程池可以進(jìn)行統(tǒng)一分配、調(diào)優(yōu)和監(jiān)控朵锣。假設(shè)一個(gè)服務(wù)器一天要處理 50000 個(gè)請(qǐng)求谬盐,并且每個(gè)請(qǐng)求需要一個(gè)單獨(dú)的線程完成。在線程池中诚些,線程數(shù)一般是固定的飞傀,所以產(chǎn)生線程總數(shù)不會(huì)超過線程池中線程的數(shù)目,而如果服務(wù)器不利用線程池來(lái)處理這些請(qǐng)求則線程總數(shù)為50000诬烹。一般線程池大小是遠(yuǎn)小于 50000砸烦。所以利用線程池的服務(wù)器程序不會(huì)為了創(chuàng)建50000 而在處理請(qǐng)求時(shí)浪費(fèi)時(shí)間,從而提高效率椅您。

二外冀、ThreadPoolExecutor 的類關(guān)系

Executor 是一個(gè)接口,它是 Executor 框架的基礎(chǔ)掀泳,它將任務(wù)的提交與任務(wù)的執(zhí)行分離開來(lái)雪隧。

ExecutorService 接口繼承了 Executor,在其上做了一些 shutdown()员舵、submit()的擴(kuò)展脑沿,可以說是真正的線程池接口;

AbstractExecutorService 抽象類實(shí)現(xiàn)了 ExecutorService 接口中的大部分方法马僻;

ThreadPoolExecutor 是線程池的核心實(shí)現(xiàn)類庄拇,用來(lái)執(zhí)行被提交的任務(wù)。

ScheduledExecutorService 接口繼承了 ExecutorService 接口韭邓,提供了帶"周期執(zhí)行"功能 ExecutorService措近;

ScheduledThreadPoolExecutor是一個(gè)實(shí)現(xiàn)類,可以在給定的延遲后運(yùn)行命令女淑,或者定期執(zhí)行命令瞭郑。ScheduledThreadPoolExecutor 比 Timer 更靈活,功能更強(qiáng)大鸭你。

三屈张、線程池的創(chuàng)建各個(gè)參數(shù)含義

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long

keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory

threadFactory,RejectedExecutionHandler handler)

1、corePoolSize

線程池中的核心線程數(shù)袱巨,當(dāng)提交一個(gè)任務(wù)時(shí)阁谆,線程池創(chuàng)建一個(gè)新線程執(zhí)行任務(wù),直到當(dāng)前線程數(shù)等于 corePoolSize愉老;

如果當(dāng)前線程數(shù)為 corePoolSize场绿,繼續(xù)提交的任務(wù)被保存到阻塞隊(duì)列中,等待被執(zhí)行嫉入;

如果執(zhí)行了線程池的 prestartAllCoreThreads()方法裳凸,線程池會(huì)提前創(chuàng)建并啟動(dòng)所有核心線程贱鄙。

2、maximumPoolSize

線程池中允許的最大線程數(shù)姨谷。如果當(dāng)前阻塞隊(duì)列滿了逗宁,且繼續(xù)提交任務(wù),則創(chuàng)建新的線程執(zhí)行任務(wù)梦湘,前提是當(dāng)前線程數(shù)小于 maximumPoolSize

3瞎颗、keepAliveTime

線程空閑時(shí)的存活時(shí)間,即當(dāng)線程沒有任務(wù)執(zhí)行時(shí)捌议,繼續(xù)存活的時(shí)間哼拔。默認(rèn)情況下,該參數(shù)只在線程數(shù)大于 corePoolSize 時(shí)才有用

TimeUnit

keepAliveTime 的時(shí)間單位

workQueue

workQueue 必須是 BlockingQueue 阻塞隊(duì)列瓣颅。當(dāng)線程池中的線程數(shù)超過它的corePoolSize 的時(shí)候倦逐,線程會(huì)進(jìn)入阻塞隊(duì)列進(jìn)行阻塞等待。通過 workQueue宫补,線程池實(shí)現(xiàn)了阻塞功能

4檬姥、workQueue

用于保存等待執(zhí)行的任務(wù)的阻塞隊(duì)列,一般來(lái)說粉怕,我們應(yīng)該盡量使用有界隊(duì)列健民,因?yàn)槭褂脽o(wú)界隊(duì)列作為工作隊(duì)列會(huì)對(duì)線程池帶來(lái)如下影響。

1)當(dāng)線程池中的線程數(shù)達(dá)到 corePoolSize 后贫贝,新任務(wù)將在無(wú)界隊(duì)列中等待秉犹,因此線程池中的線程數(shù)不會(huì)超過 corePoolSize。

2)由于 1稚晚,使用無(wú)界隊(duì)列時(shí) maximumPoolSize 將是一個(gè)無(wú)效參數(shù)崇堵。

3)由于 1 和 2,使用無(wú)界隊(duì)列時(shí) keepAliveTime 將是一個(gè)無(wú)效參數(shù)客燕。

4)更重要的鸳劳,使用無(wú)界 queue 可能會(huì)耗盡系統(tǒng)資源,有界隊(duì)列則有助于防止資源耗盡幸逆,同時(shí)即使使用有界隊(duì)列,也要盡量控制隊(duì)列的大小在一個(gè)合適的范圍暮现。

所以我們一般會(huì)使用还绘,ArrayBlockingQueue、LinkedBlockingQueue栖袋、

SynchronousQueue拍顷、PriorityBlockingQueue。

5塘幅、threadFactory

創(chuàng)建線程的工廠昔案,通過自定義的線程工廠可以給每個(gè)新建的線程設(shè)置一個(gè)具有識(shí)別度的線程名尿贫,當(dāng)然還可以更加自由的對(duì)線程做更多的設(shè)置,比如設(shè)置所有的線程為守護(hù)線程踏揣。參見代碼 cn.enjoyedu.ch6. ThreadPoolAdv

Executors 靜態(tài)工廠里默認(rèn)的 threadFactory庆亡,線程的命名規(guī)則是“pool-數(shù)字-thread-數(shù)字”。

6捞稿、RejectedExecutionHandler

線程池的飽和策略又谋,當(dāng)阻塞隊(duì)列滿了,且沒有空閑的工作線程娱局,如果繼續(xù)提交任務(wù)彰亥,必須采取一種策略處理該任務(wù),線程池提供了 4 種策略:

(1)AbortPolicy:直接拋出異常衰齐,默認(rèn)策略任斋;

(2)CallerRunsPolicy:用調(diào)用者所在的線程來(lái)執(zhí)行任務(wù);

(3)DiscardOldestPolicy:丟棄阻塞隊(duì)列中靠最前的任務(wù)耻涛,并執(zhí)行當(dāng)前任務(wù)废酷;

(4)DiscardPolicy:直接丟棄任務(wù);

當(dāng)然也可以根據(jù)應(yīng)用場(chǎng)景實(shí)現(xiàn) RejectedExecutionHandler 接口犬第,自定義飽和策略锦积,如記錄日志或持久化存儲(chǔ)不能處理的任務(wù)。?

四歉嗓、擴(kuò)展線程池

能擴(kuò)展線程池的功能嗎丰介?比如在任務(wù)執(zhí)行的前后做一點(diǎn)我們自己的業(yè)務(wù)工作?實(shí)際上鉴分,JDK 的線程池已經(jīng)為我們預(yù)留的接口哮幢,在線程池核心方法中,有 2個(gè)方法是空的志珍,就是給我們預(yù)留的橙垢。還有一個(gè)線程池退出時(shí)會(huì)調(diào)用的方法。參見代碼 cn.enjoyedu.ch6. ThreadPoolExt可以看到伦糯,每個(gè)任務(wù)執(zhí)行前后都會(huì)調(diào)用 beforeExecute 和 afterExecute 方法柜某。相當(dāng)于執(zhí)行了一個(gè)切面。而在調(diào)用 shutdown 方法后則會(huì)調(diào)用 terminated 方法敛纲。

五喂击、線程池的工作機(jī)制

1)如果當(dāng)前運(yùn)行的線程少于 corePoolSize,則創(chuàng)建新線程來(lái)執(zhí)行任務(wù)(注意淤翔,執(zhí)行這一步驟需要獲取全局鎖)翰绊。

2)如果運(yùn)行的線程等于或多于 corePoolSize,則將任務(wù)加入 BlockingQueue。

3)如果無(wú)法將任務(wù)加入 BlockingQueue(隊(duì)列已滿)监嗜,則創(chuàng)建新的線程來(lái)處理任務(wù)谐檀。

4)如果創(chuàng)建新線程將使當(dāng)前運(yùn)行的線程超出 maximumPoolSize,任務(wù)將被拒絕裁奇,并調(diào)用 RejectedExecutionHandler.rejectedExecution()方法桐猬。?

六、提交任務(wù)

execute()方法用于提交不需要返回值的任務(wù)框喳,所以無(wú)法判斷任務(wù)是否被線程池執(zhí)行成功课幕。submit()方法用于提交需要返回值的任務(wù)。線程池會(huì)返回一個(gè) future 類型的對(duì)象五垮,通過這個(gè) future 對(duì)象可以判斷任務(wù)是否執(zhí)行成功乍惊,并且可以通過 future的 get()方法來(lái)獲取返回值,get()方法會(huì)阻塞當(dāng)前線程直到任務(wù)完成放仗,而使用 get(long timeout润绎,TimeUnit unit)方法則會(huì)阻塞當(dāng)前線程一段時(shí)間后立即返回,這時(shí)候有可能任務(wù)沒有執(zhí)行完诞挨。

七莉撇、關(guān)閉線程池

可以通過調(diào)用線程池的 shutdown 或 shutdownNow 方法來(lái)關(guān)閉線程池。它們的原理是遍歷線程池中的工作線程惶傻,然后逐個(gè)調(diào)用線程的 interrupt 方法來(lái)中斷線程棍郎,所以無(wú)法響應(yīng)中斷的任務(wù)可能永遠(yuǎn)無(wú)法終止。但是它們存在一定的區(qū)別银室,shutdownNow 首先將線程池的狀態(tài)設(shè)置成 STOP涂佃,然后嘗試停止所有的正在執(zhí)行或暫停任務(wù)的線程,并返回等待執(zhí)行任務(wù)的列表蜈敢,而shutdown 只是將線程池的狀態(tài)設(shè)置成 SHUTDOWN 狀態(tài)辜荠,然后中斷所有沒有正在執(zhí)行任務(wù)的線程。只要調(diào)用了這兩個(gè)關(guān)閉方法中的任意一個(gè)抓狭,isShutdown 方法就會(huì)返回 true伯病。當(dāng)所有的任務(wù)都已關(guān)閉后,才表示線程池關(guān)閉成功否过,這時(shí)調(diào)用 isTerminaed 方法會(huì)返回 true午笛。至于應(yīng)該調(diào)用哪一種方法來(lái)關(guān)閉線程池,應(yīng)該由提交到線程池的任務(wù)特性決定苗桂,通常調(diào)用 shutdown 方法來(lái)關(guān)閉線程池药磺,如果任務(wù)不一定要執(zhí)行完,則可以調(diào)用 shutdownNow 方法誉察。?

八与涡、合理地配置線程池

要想合理地配置線程池惹谐,就必須首先分析任務(wù)特性要想合理地配置線程池持偏,就必須首先分析任務(wù)特性驼卖,可以從以下幾個(gè)角度來(lái)分析。

?任務(wù)的性質(zhì):CPU 密集型任務(wù)鸿秆、IO 密集型任務(wù)和混合型任務(wù)酌畜。

?任務(wù)的優(yōu)先級(jí):高、中和低卿叽。

?任務(wù)的執(zhí)行時(shí)間:長(zhǎng)桥胞、中和短。

?任務(wù)的依賴性:是否依賴其他系統(tǒng)資源考婴,如數(shù)據(jù)庫(kù)連接贩虾。

性質(zhì)不同的任務(wù)可以用不同規(guī)模的線程池分開處理。

CPU 密集型任務(wù)應(yīng)配置盡可能小的線程沥阱,如配置 Ncpu+1 個(gè)線程的線程池缎罢。

由于 IO 密集型任務(wù)線程并不是一直在執(zhí)行任務(wù),則應(yīng)配置盡可能多的線程考杉,如

2*Ncpu策精。

混合型的任務(wù),如果可以拆分崇棠,將其拆分成一個(gè) CPU 密集型任務(wù)和一個(gè) IO

密集型任務(wù)咽袜,只要這兩個(gè)任務(wù)執(zhí)行的時(shí)間相差不是太大,那么分解后執(zhí)行的吞吐

量將高于串行執(zhí)行的吞吐量枕稀。如果這兩個(gè)任務(wù)執(zhí)行時(shí)間相差太大询刹,則沒必要進(jìn)行

分解〕槔常可以通過 Runtime.getRuntime().availableProcessors()方法獲得當(dāng)前設(shè)備的

CPU 個(gè)數(shù)范抓。

對(duì)于 IO 型的任務(wù)的最佳線程數(shù),有個(gè)公式可以計(jì)算

Nthreads = NCPU * UCPU * (1 + W/C)

其中:

?NCPU 是處理器的核的數(shù)目

?UCPU 是期望的 CPU 利用率(該值應(yīng)該介于 0 和 1 之間)

?W/C 是等待時(shí)間與計(jì)算時(shí)間的比率

等待時(shí)間與計(jì)算時(shí)間我們?cè)?Linux 下使用相關(guān)的 vmstat 命令或者 top 命令查

看食铐。

優(yōu)先級(jí)不同的任務(wù)可以使用優(yōu)先級(jí)隊(duì)列 PriorityBlockingQueue 來(lái)處理匕垫。它可

以讓優(yōu)先級(jí)高的任務(wù)先執(zhí)行。

執(zhí)行時(shí)間不同的任務(wù)可以交給不同規(guī)模的線程池來(lái)處理虐呻,或者可以使用優(yōu)先

級(jí)隊(duì)列象泵,讓執(zhí)行時(shí)間短的任務(wù)先執(zhí)行。

依賴數(shù)據(jù)庫(kù)連接池的任務(wù)斟叼,因?yàn)榫€程提交 SQL 后需要等待數(shù)據(jù)庫(kù)返回結(jié)果偶惠,

等待的時(shí)間越長(zhǎng),則 CPU 空閑時(shí)間就越長(zhǎng)朗涩,那么線程數(shù)應(yīng)該設(shè)置得越大忽孽,這樣

才能更好地利用 CPU陨倡。

建議使用有界隊(duì)列。有界隊(duì)列能增加系統(tǒng)的穩(wěn)定性和預(yù)警能力源哩,可以根據(jù)需

要設(shè)大一點(diǎn)兒鸳惯,比如幾千。

假設(shè)出革,我們現(xiàn)在有一個(gè) Web 系統(tǒng)造壮,里面使用了線程池來(lái)處理業(yè)務(wù),在某些

情況下骂束,系統(tǒng)里后臺(tái)任務(wù)線程池的隊(duì)列和線程池全滿了耳璧,不斷拋出拋棄任務(wù)的異

常,通過排查發(fā)現(xiàn)是數(shù)據(jù)庫(kù)出現(xiàn)了問題展箱,導(dǎo)致執(zhí)行 SQL 變得非常緩慢旨枯,因?yàn)楹笈_(tái)

任務(wù)線程池里的任務(wù)全是需要向數(shù)據(jù)庫(kù)查詢和插入數(shù)據(jù)的,所以導(dǎo)致線程池里的

工作線程全部阻塞混驰,任務(wù)積壓在線程池里召廷。

如果當(dāng)時(shí)我們?cè)O(shè)置成無(wú)界隊(duì)列,那么線程池的隊(duì)列就會(huì)越來(lái)越多账胧,有可能會(huì)

撐滿內(nèi)存竞慢,導(dǎo)致整個(gè)系統(tǒng)不可用,而不只是后臺(tái)任務(wù)出現(xiàn)問題治泥。?

九筹煮、預(yù)定義線程池

FixedThreadPool 詳解

創(chuàng)建使用固定線程數(shù)的 FixedThreadPool 的 API。適用于為了滿足資源管理的

需求居夹,而需要限制當(dāng)前線程數(shù)量的應(yīng)用場(chǎng)景败潦,它適用于負(fù)載比較重的服務(wù)器。

FixedThreadPool 的 corePoolSize 和 maximumPoolSize 都被設(shè)置為創(chuàng)建

FixedThreadPool 時(shí)指定的參數(shù) nThreads准脂。

當(dāng)線程池中的線程數(shù)大于 corePoolSize 時(shí)劫扒,keepAliveTime 為多余的空閑線程

等待新任務(wù)的

最長(zhǎng)時(shí)間,超過這個(gè)時(shí)間后多余的線程將被終止狸膏。這里把 keepAliveTime 設(shè)

置為 0L沟饥,意味著多余的空閑線程會(huì)被立即終止。

FixedThreadPool 使用有界隊(duì)列 LinkedBlockingQueue 作為線程池的工作隊(duì)列

(隊(duì)列的容量為 Integer.MAX_VALUE)湾戳。

SingleThreadExecutor

創(chuàng)建使用單個(gè)線程的 SingleThread-Executor 的 API贤旷,于需要保證順序地執(zhí)行

各個(gè)任務(wù);并且在任意時(shí)間點(diǎn)砾脑,不會(huì)有多個(gè)線程是活動(dòng)的應(yīng)用場(chǎng)景幼驶。

corePoolSize 和 maximumPoolSize 被設(shè)置為 1。其他參數(shù)與 FixedThreadPool

相同韧衣。SingleThreadExecutor 使用有界隊(duì)列 LinkedBlockingQueue 作為線程池的工

作隊(duì)列(隊(duì)列的容量為 Integer.MAX_VALUE)盅藻。

CachedThreadPool

創(chuàng)建一個(gè)會(huì)根據(jù)需要?jiǎng)?chuàng)建新線程的 CachedThreadPool 的 API购桑。大小無(wú)界的線

程池,適用于執(zhí)行很多的短期異步任務(wù)的小程序氏淑,或者是負(fù)載較輕的服務(wù)器其兴。

corePoolSize 被設(shè)置為 0,即 corePool 為空夸政;maximumPoolSize 被設(shè)置為

Integer.MAX_VALUE。這里把 keepAliveTime 設(shè)置為 60L榴徐,意味著 CachedThreadPool

中的空閑線程等待新任務(wù)的最長(zhǎng)時(shí)間為60秒守问,空閑線程超過60秒后將會(huì)被終止。

FixedThreadPool 和 SingleThreadExecutor 使用有界隊(duì)列 LinkedBlockingQueue

作為線程池的工作隊(duì)列坑资。CachedThreadPool 使用沒有容量的 SynchronousQueue

作為線程池的工作隊(duì)列耗帕,但 CachedThreadPool 的 maximumPool 是無(wú)界的。這意

味著袱贮,如果主線程提交任務(wù)的速度高于 maximumPool 中線程處理任務(wù)的速度時(shí)仿便,

CachedThreadPool 會(huì)不斷創(chuàng)建新線程。極端情況下攒巍,CachedThreadPool 會(huì)因?yàn)閯?chuàng)

建過多線程而耗盡 CPU 和內(nèi)存資源嗽仪。 WorkStealingPool

利用所有運(yùn)行的處理器數(shù)目來(lái)創(chuàng)建一個(gè)工作竊取的線程池,使用 forkjoin 實(shí)

現(xiàn)

ScheduledThreadPoolExecutor

使用工廠類 Executors 來(lái)創(chuàng)建柒莉。Executors 可以創(chuàng)建 2 種類型的

ScheduledThreadPoolExecutor闻坚,如下。

?ScheduledThreadPoolExecutor兢孝。包含若干個(gè)線程的

ScheduledThreadPoolExecutor窿凤。

?SingleThreadScheduledExecutor。只包含一個(gè)線程的

ScheduledThreadPoolExecutor跨蟹。

ScheduledThreadPoolExecutor 適用于需要多個(gè)后臺(tái)線程執(zhí)行周期任務(wù)雳殊,同時(shí)

為了滿足資源管理的需求而需要限制后臺(tái)線程的數(shù)量的應(yīng)用場(chǎng)景。

SingleThreadScheduledExecutor 適用于需要單個(gè)后臺(tái)線程執(zhí)行周期任務(wù)窗轩,同

時(shí)需要保證順序地執(zhí)行各個(gè)任務(wù)的應(yīng)用場(chǎng)景夯秃。

提交定時(shí)任務(wù)

public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit

unit)

//向定時(shí)任務(wù)線程池提交一個(gè)延時(shí) Runnable 任務(wù)(僅執(zhí)行一次)

public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit);

//向定時(shí)任務(wù)線程池提交一個(gè)延時(shí)的 Callable 任務(wù)(僅執(zhí)行一次)

public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long

initialDelay, long period, TimeUnit unit)

//向定時(shí)任務(wù)線程池提交一個(gè)固定時(shí)間間隔執(zhí)行的任務(wù)

public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long

initialDelay, long delay, TimeUnit unit);

//向定時(shí)任務(wù)線程池提交一個(gè)固定延時(shí)間隔執(zhí)行的任務(wù)

固定時(shí)間間隔的任務(wù)不論每次任務(wù)花費(fèi)多少時(shí)間,下次任務(wù)開始執(zhí)行時(shí)間從

理論上講是確定的痢艺,當(dāng)然執(zhí)行任務(wù)的時(shí)間不能超過執(zhí)行周期寝并。

固定延時(shí)間隔的任務(wù)是指每次執(zhí)行完任務(wù)以后都延時(shí)一個(gè)固定的時(shí)間。由于

操作系統(tǒng)調(diào)度以及每次任務(wù)執(zhí)行的語(yǔ)句可能不同腹备,所以每次任務(wù)執(zhí)行所花費(fèi)的時(shí)

間是不確定的衬潦,也就導(dǎo)致了每次任務(wù)的執(zhí)行周期存在一定的波動(dòng)。

定時(shí)任務(wù)超時(shí)問題

scheduleAtFixedRate 中植酥,若任務(wù)處理時(shí)長(zhǎng)超出設(shè)置的定時(shí)頻率時(shí)長(zhǎng)镀岛,本次任

務(wù)執(zhí)行完才開始下次任務(wù)弦牡,下次任務(wù)已經(jīng)處于超時(shí)狀態(tài),會(huì)馬上開始執(zhí)行漂羊。

若任務(wù)處理時(shí)長(zhǎng)小于定時(shí)頻率時(shí)長(zhǎng)驾锰,任務(wù)執(zhí)行完后,定時(shí)器等待走越,下次任務(wù)

會(huì)在定時(shí)器等待頻率時(shí)長(zhǎng)后執(zhí)行椭豫。

如下例子:

設(shè)置定時(shí)任務(wù)每 60s 執(zhí)行一次,那么從理論上應(yīng)該第一次任務(wù)在第 0s 開始, 第二次任務(wù)在第 60s 開始旨指,第三次任務(wù)在 120s 開始赏酥,但實(shí)際運(yùn)行時(shí)第一次任務(wù)

時(shí)長(zhǎng) 80s,第二次任務(wù)時(shí)長(zhǎng) 30s谆构,第三次任務(wù)時(shí)長(zhǎng) 50s裸扶,則實(shí)際運(yùn)行結(jié)果為:

第一次任務(wù)第 0s 開始,第 80s 結(jié)束;

第二次任務(wù)第 80s 開始,第 110s 結(jié)束(上次任務(wù)已超時(shí),本次不會(huì)再等待 60s, 會(huì)馬上開始)搬素;

第三次任務(wù)第 120s 開始,第 170s 結(jié)束. 第四次任務(wù)第 180s 開始..... 具體代碼呵晨,參見 cn.enjoyedu.ch6.schd. ScheduleWorkerTime

十、CompletionService

CompletionService 實(shí)際上可以看做是 Executor 和 BlockingQueue 的結(jié)合體熬尺。

CompletionService 在接收到要執(zhí)行的任務(wù)時(shí)摸屠,通過類似 BlockingQueue 的 put 和

take 獲得任務(wù)執(zhí)行的結(jié)果。

CompletionService 的一個(gè)實(shí)現(xiàn)是 ExecutorCompletionService粱哼,

ExecutorCompletionService 把具體的計(jì)算任務(wù)交給 Executor 完成餐塘。

在實(shí)現(xiàn)上,ExecutorCompletionService 在構(gòu)造函數(shù)中會(huì)創(chuàng)建一個(gè)

BlockingQueue(使用的基于鏈表的 LinkedBlockingQueue)皂吮,該 BlockingQueue 的

作用是保存 Executor 執(zhí)行的結(jié)果戒傻。

當(dāng)提交一個(gè)任務(wù)到 ExecutorCompletionService 時(shí),首先將任務(wù)包裝成

QueueingFuture蜂筹,它是 FutureTask 的一個(gè)子類需纳,然后改寫 FutureTask 的 done 方

法,之后把 Executor 執(zhí)行的計(jì)算結(jié)果放入 BlockingQueue 中艺挪。

與 ExecutorService 最主要的區(qū)別在于 submit 的 task 不一定是按照加入時(shí)的

順序完成的不翩。CompletionService 對(duì) ExecutorService 進(jìn)行了包裝,內(nèi)部維護(hù)一個(gè)保

存 Future 對(duì)象的 BlockingQueue麻裳。只有當(dāng)這個(gè) Future 對(duì)象狀態(tài)是結(jié)束的時(shí)候口蝠,才

會(huì)加入到這個(gè) Queue 中,take()方法其實(shí)就是 Producer-Consumer 中的 Consumer津坑。

它會(huì)從 Queue 中取出 Future 對(duì)象妙蔗,如果 Queue 是空的,就會(huì)阻塞在那里疆瑰,直到

有完成的 Future 對(duì)象加入到 Queue 中眉反。所以昙啄,先完成的必定先被取出。這樣就

減少了不必要的等待時(shí)間寸五。

參見代碼 cn.enjoyedu.ch6.comps. CompletionCase梳凛,我們可以得出結(jié)論:

使用方法一,自己創(chuàng)建一個(gè)集合來(lái)保存 Future 存根并循環(huán)調(diào)用其返回結(jié)果

的時(shí)候梳杏,主線程并不能保證首先獲得的是最先完成任務(wù)的線程返回值韧拒。它只是按

加入線程池的順序返回。因?yàn)?take 方法是阻塞方法十性,后面的任務(wù)完成了叛溢,前面

的任務(wù)卻沒有完成,主程序就那樣等待在那兒烁试,只到前面的完成了,它才知道原

來(lái)后面的也完成了拢肆。

使用方法二减响,使用 CompletionService 來(lái)維護(hù)處理線程不的返回結(jié)果時(shí),主

線程總是能夠拿到最先完成的任務(wù)的返回值郭怪,而不管它們加入線程池的順序支示。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市鄙才,隨后出現(xiàn)的幾起案子颂鸿,更是在濱河造成了極大的恐慌,老刑警劉巖攒庵,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嘴纺,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡浓冒,警方通過查閱死者的電腦和手機(jī)栽渴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)稳懒,“玉大人闲擦,你說我怎么就攤上這事〕“穑” “怎么了墅冷?”我有些...
    開封第一講書人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)或油。 經(jīng)常有香客問我寞忿,道長(zhǎng),這世上最難降的妖魔是什么顶岸? 我笑而不...
    開封第一講書人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任罐脊,我火速辦了婚禮定嗓,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘萍桌。我一直安慰自己宵溅,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開白布上炎。 她就那樣靜靜地躺著恃逻,像睡著了一般。 火紅的嫁衣襯著肌膚如雪藕施。 梳的紋絲不亂的頭發(fā)上寇损,一...
    開封第一講書人閱讀 51,182評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音裳食,去河邊找鬼矛市。 笑死,一個(gè)胖子當(dāng)著我的面吹牛诲祸,可吹牛的內(nèi)容都是我干的浊吏。 我是一名探鬼主播,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼救氯,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼找田!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起着憨,我...
    開封第一講書人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤墩衙,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后甲抖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體漆改,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年准谚,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了籽懦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡氛魁,死狀恐怖暮顺,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情秀存,我是刑警寧澤捶码,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站或链,受9級(jí)特大地震影響惫恼,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜澳盐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一祈纯、第九天 我趴在偏房一處隱蔽的房頂上張望令宿。 院中可真熱鬧,春花似錦腕窥、人聲如沸粒没。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)癞松。三九已至,卻和暖如春入蛆,著一層夾襖步出監(jiān)牢的瞬間响蓉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工哨毁, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留枫甲,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓扼褪,卻偏偏與公主長(zhǎng)得像想幻,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子迎捺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353