? ? ? ?使用線程的時候就去創(chuàng)建一個線程,這樣實現(xiàn)起來非常簡便,但是就會有一個問題:
如果并發(fā)的線程數(shù)量很多,并且每個線程都是執(zhí)行一個時間很短的任務就結(jié)束了迹淌,這樣頻繁創(chuàng)建線程就會大大降低系統(tǒng)的效率,因為頻繁創(chuàng)建線程和銷毀線程需要時間己单。
那么有沒有一種辦法使得線程可以復用唉窃,就是執(zhí)行完一個任務,并不被銷毀纹笼,而是可以繼續(xù)執(zhí)行其他的任務纹份?在Java中可以通過線程池來達到這樣的效果。
合理利用線程池能夠帶來三個好處。第一:降低資源消耗蔓涧。通過重復利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗件已。第二:提高響應速度。當任務到達時元暴,任務可以不需要的等到線程創(chuàng)建就能立即執(zhí)行篷扩。第三:提高線程的可管理性。線程是稀缺資源茉盏,如果無限制的創(chuàng)建鉴未,不僅會消耗系統(tǒng)資源,還會降低系統(tǒng)的穩(wěn)定性鸠姨,使用線程池可以進行統(tǒng)一的分配铜秆,調(diào)優(yōu)和監(jiān)控。
線程池繼承關系圖:
ThreadPoolExecutor享怀、AbstractExecutorService羽峰、ExecutorService和Executor幾個之間的關系:
Executor是一個頂層接口趟咆,在它里面只聲明了一個方法execute(Runnable)添瓷,返回值為void,參數(shù)為Runnable類型值纱,從字面意思可以理解鳞贷,就是用來執(zhí)行傳進去的任務的;
然后ExecutorService接口繼承了Executor接口虐唠,并聲明了一些方法:submit搀愧、invokeAll、invokeAny以及shutDown等疆偿,也提供了更加全面的提交任務機制咱筛,如返回Future而不是void的submit方法。注意杆故,這個方法的輸入是Callable迅箩,它解決了Runnable無法返回結(jié)果的困擾;
抽象類AbstractExecutorService實現(xiàn)了ExecutorService接口处铛,基本實現(xiàn)了ExecutorService中聲明的所有方法饲趋;
然后ThreadPoolExecutor繼承了類AbstractExecutorService。
? ? ? ?Java標準庫提供了幾種基礎實現(xiàn)撤蟆,比如ThreadPoolExecutor奕塑、ScheduledThreadPoolExecutor、ForkJoinPool家肯。這些線程池的設計特點在于其高度的可調(diào)節(jié)性和靈活性龄砰,以盡量滿足復雜多變的實際應用場景。
? ? ? ? Executors則從簡化使用的角度讨衣,為我們提供了各種方便的靜態(tài)工廠方法寝贡。
java.uitl.concurrent.ThreadPoolExecutor類是線程池中最核心的一個類扒披,因此如果要透徹地了解Java中的線程池,必須先了解這個類圃泡。
public?ThreadPoolExecutor(int?corePoolSize, int?maximumPoolSize,long?keepAliveTime, TimeUnit unit, BlockingQueueworkQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler);
參數(shù)的含義:
corePoolSize:線程池的基本大小碟案。默認情況下,在創(chuàng)建了線程池后颇蜡,線程池中的線程數(shù)為0价说,當有任務來之后,就會創(chuàng)建一個線程去執(zhí)行任務风秤,當線程池中的線程數(shù)目達到corePoolSize后鳖目,就會把到達的任務放到緩存隊列當中;
maximumPoolSize:線程池中允許的最大線程數(shù)缤弦。如果隊列滿了领迈,并且已創(chuàng)建的線程數(shù)小于最大線程數(shù),則線程池會再創(chuàng)建新的線程執(zhí)行任務碍沐。值得注意的是如果使用了無界的任務隊列這個參數(shù)就沒什么效果狸捅。注意還有一個largestPoolSize,記錄了曾經(jīng)出現(xiàn)的最大線程個數(shù)累提。因為setMaximumPoolSize()可以改變最大線程數(shù)尘喝。
poolSize:線程池中當前線程的數(shù)量。
keepAliveTime:表示線程沒有任務執(zhí)行時最多保持多久時間會終止斋陪。默認情況下朽褪,只有當線程池中的線程數(shù)大于corePoolSize時,keepAliveTime才會起作用无虚,直到線程池中的線程數(shù)不大于corePoolSize缔赠,即當線程池中的線程數(shù)大于corePoolSize時,如果一個線程空閑的時間達到keepAliveTime友题,則會終止嗤堰,直到線程池中的線程數(shù)不超過corePoolSize。但是如果調(diào)用了allowCoreThreadTimeOut(boolean)方法咆爽,在線程池中的線程數(shù)不大于corePoolSize時梁棠,keepAliveTime參數(shù)也會起作用,直到線程池中的線程數(shù)為0斗埂;
workQueue(任務隊列):用于保存等待執(zhí)行的任務的阻塞隊列符糊。可以選擇以下幾個阻塞隊列:
(1)ArrayBlockingQueue:是一個基于數(shù)組結(jié)構(gòu)的有界阻塞隊列呛凶,此隊列按 FIFO(先進先出)原則對元素進行排序男娄。
(2)LinkedBlockingQueue:一個基于鏈表結(jié)構(gòu)的阻塞隊列,此隊列按FIFO (先進先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue模闲。其實也是有界隊列建瘫,但是不設置大小時就時Integer.MAX_VALUE。靜態(tài)工廠方法Executors.newFixedThreadPool()和Executors.newSingleThreadExecutor()使用了這個隊列尸折。
(3)SynchronousQueue:一個不存儲元素的阻塞隊列啰脚。每個插入操作必須等到另一個線程調(diào)用移除操作,否則插入操作一直處于阻塞狀態(tài)实夹,吞吐量通常要高于LinkedBlockingQueue橄浓,靜態(tài)工廠方法Executors.newCachedThreadPool使用了這個隊列。
(4)PriorityBlockingQueue:一個具有優(yōu)先級得無界阻塞隊列亮航。
handler(任務拒絕策略):
當線程池的任務緩存隊列已滿并且線程池中的線程數(shù)目達到maximumPoolSize荸实,如果還有任務到來就會采取任務拒絕策略,通常有以下四種策略:
ThreadPoolExecutor.AbortPolicy:丟棄任務并拋出RejectedExecutionException異常缴淋。
ThreadPoolExecutor.DiscardPolicy:也是丟棄任務准给,但是不拋出異常。
ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務重抖,然后重新嘗試執(zhí)行任務(重復此過程)
ThreadPoolExecutor.CallerRunsPolicy:由調(diào)用線程處理該任務
那么poolSize露氮、corePoolSize、maximumPoolSize三者的關系是如何的呢仇哆?
有界隊列:
當新提交一個任務時:
(1)如果poolSize < corePoolSize沦辙,新增加一個線程處理新的任務夫植。
(2)如果poolSize = corePoolSize讹剔,新任務會被放入阻塞隊列等待。
(3)如果阻塞隊列的容量達到上限详民,且這時poolSize < maximumPoolSize延欠,新增線程來處理任務。
(4)如果阻塞隊列滿了沈跨,且poolSize = maximumPoolSize由捎,那么線程池已經(jīng)達到極限,會根據(jù)飽和策略RejectedExecutionHandler拒絕新的任務饿凛。
(5)如果線程池中的線程數(shù)量大于 corePoolSize時狞玛,且某線程空閑時間超過keepAliveTime,線程將被終止涧窒,直至線程池中的線程數(shù)目不大于corePoolSize心肪;如果允許為核心池中的線程設置存活時間,那么核心池中的線程空閑時間超過keepAliveTime纠吴,線程也會被終止硬鞍。
無界隊列
與有界隊列相比,除非系統(tǒng)資源耗盡,否則無界的任務隊列不存在任務入隊失敗的情況固该。當有新的任務到來锅减,系統(tǒng)的線程數(shù)小于corePoolSize時,則新建線程執(zhí)行任務伐坏。當達到corePoolSize后怔匣,就不會繼續(xù)增加,若后續(xù)仍有新的任務加入桦沉,而沒有空閑的線程資源劫狠,則任務直接進入隊列等待。若任務創(chuàng)建和處理的速度差異很大永部,無界隊列會保持快速增長独泞,直到耗盡系統(tǒng)內(nèi)存。
https://blog.csdn.net/kusedexingfu/article/details/72491864
幾個工廠方法:
不過在java doc中苔埋,并不提倡我們直接使用ThreadPoolExecutor懦砂,而是使用Executors類中提供的幾個靜態(tài)方法來創(chuàng)建線程池:
Executors.newCachedThreadPool();??????? //創(chuàng)建一個線程池,容量大小為??? ?????
????????????????????????????????????????????????????????????????????Integer.MAX_VALUE
Executors.newSingleThreadExecutor();?? //創(chuàng)建容量為1的線程池
Executors.newFixedThreadPool(intnThreads);??? //創(chuàng)建固定容量大小的線程池
Executors.newSingleThreadScheduledExecutor()和newScheduledThreadPool(int corePoolSize)??//創(chuàng)建的是一個ScheduledExecutorService组橄,可以進行定時或周期性的工作調(diào)度荞膘,區(qū)別在于單一工作線程還是多個工作線程。
Executors.newWorkStealingPool(intparallelism)????//這是一個經(jīng)常被人忽略的線程池玉工,Java8才加入這個創(chuàng)建方法羽资,其內(nèi)部會構(gòu)建ForkJoinPool,利用Work-Stealing算法遵班,并行的處理任務屠升,不保證處理順序。
實現(xiàn)細節(jié):
public static ExecutorService newFixedThreadPool(int nThreads) {
??? return newThreadPoolExecutor(nThreads, nThreads,
?????????????????????????????????0L, TimeUnit.MILLISECONDS,
?????????????????????????????????new LinkedBlockingQueue());
}
public static ExecutorService newSingleThreadExecutor() {
??? return newFinalizableDelegatedExecutorService
??????? (newThreadPoolExecutor(1, 1,
???????????????????????????????0L, TimeUnit.MILLISECONDS,
???????????????????????????????new LinkedBlockingQueue()));
}
public static ExecutorService newCachedThreadPool() {
??? return newThreadPoolExecutor(0, Integer.MAX_VALUE,
?????????????????????????????????60L, TimeUnit.SECONDS,
?????????????????????????????????new SynchronousQueue());
}
從它們的具體實現(xiàn)來看狭郑,它們實際上也是調(diào)用了ThreadPoolExecutor腹暖,只不過參數(shù)都已配置好了。
newFixedThreadPool創(chuàng)建的線程池corePoolSize和maximumPoolSize值是相等的翰萨,它使用的LinkedBlockingQueue脏答;
newSingleThreadExecutor將corePoolSize和maximumPoolSize都設置為1,也使用的LinkedBlockingQueue亩鬼;
newCachedThreadPool將corePoolSize設置為0殖告,將maximumPoolSize設置為Integer.MAX_VALUE,使用的SynchronousQueue雳锋,也就是說來了任務就創(chuàng)建線程運行黄绩,當線程空閑超過60秒,就銷毀線程魄缚。
向線程池提交任務:
我們可以使用execute提交的任務宝与,但是execute方法沒有返回值焚廊,所以無法判斷任務知否被線程池執(zhí)行成功。通過以下代碼可知execute方法輸入的任務是一個Runnable類的實例习劫。
threadsPool.execute(new Runnable() {
??? @Override
??? public void run() {
??? // TODO Auto-generatedmethod stub
? ? }
});
我們也可以使用submit 方法來提交任務咆瘟,它會返回一個future,那么我們可以通過這個future來判斷任務是否執(zhí)行成功,通過future的get方法來獲取返回值诽里,
get方法會阻塞住直到任務完成袒餐,而使用get(long timeout, TimeUnit unit)方法則會阻塞一段時間后立即返回,這時有可能任務沒有執(zhí)行完谤狡。
try {
??? Object s = future.get();
??? } catch(InterruptedException e) {
??? //處理中斷異常
??? } catch(ExecutionException e) {
??? //處理無法執(zhí)行任務異常
??? } finally {
??? //關閉線程池
??? executor.shutdown();
}
線程池的關閉:
ThreadPoolExecutor提供了兩個方法灸眼,用于線程池的關閉,分別是shutdown()和shutdownNow()墓懂,其中:
shutdown():不會立即終止線程池焰宣,而是要等所有任務緩存隊列中的任務都執(zhí)行完后才終止,但再也不會接受新的任務捕仔。
shutdownNow():立即終止線程池匕积,并嘗試打斷正在執(zhí)行的任務,并且清空任務緩存隊列榜跌,返回尚未執(zhí)行的任務闪唆。
線程池的更多細節(jié)可參考:https://www.cnblogs.com/dolphin0520/p/3932921.html