摘要:最近項目中越來越多需要異步調(diào)用的地方,系統(tǒng)中雖有線程池管理,但還有可優(yōu)化的空間,通過分享該文章,幫助大家了解線程池,同時學習使用線程池開啟線程需要注意的地方。沒錯,上述方法創(chuàng)建的線程池就是。線程池就是程序中的裝修公司,代勞各種臟活累活。
最近項目中越來越多需要異步調(diào)用的地方,系統(tǒng)中雖有線程池管理,但還有可優(yōu)化的空間,通過分享該文章,幫助大家了解線程池,同時學習使用線程池開啟線程需要注意的地方。
構(gòu)造一個線程池為什么需要幾個參數(shù)?如果避免線程池出現(xiàn)OOM?Runnable和Callable的區(qū)別是什么?本文將對這些問題一一解答,同時還將給出使用線程池的常見場景和代碼片段。
Executors創(chuàng)建線程池Java中創(chuàng)建線程池很簡單,只需要調(diào)用Executors中相應(yīng)的便捷方法即可,比如Executors.newFixedThreadPool(int nThreads),但是便捷不僅隱藏了復(fù)雜性,也為我們埋下了潛在的隱患(OOM,線程耗盡)。
Executors創(chuàng)建線程池便捷方法列表:
小程序使用這些快捷方法沒什么問題,對于服務(wù)端需要長期運行的程序,創(chuàng)建線程池應(yīng)該直接使用ThreadPoolExecutor的構(gòu)造方法。沒錯,上述Executors方法創(chuàng)建的線程池就是ThreadPoolExecutor。
ThreadPoolExecutor構(gòu)造方法
Executors中創(chuàng)建線程池的快捷方法,實際上是調(diào)用了ThreadPoolExecutor的構(gòu)造方法(定時任務(wù)使用的是ScheduledThreadPoolExecutor),該類構(gòu)造方法參數(shù)列表如下:
// Java線程池的完整構(gòu)造函數(shù) public ThreadPoolExecutor( int corePoolSize, // 線程池長期維持的線程數(shù),即使線程處于Idle狀態(tài),也不會回收。 int maximumPoolSize, // 線程數(shù)的上限 long keepAliveTime, TimeUnit unit, // 超過corePoolSize的線程的idle時長, // 超過這個時間,多余的線程會被回收。 BlockingQueueworkQueue, // 任務(wù)的排隊隊列 ThreadFactory threadFactory, // 新線程的產(chǎn)生方式 RejectedExecutionHandler handler) // 拒絕策略
竟然有7個參數(shù),很無奈,構(gòu)造一個線程池確實需要這么多參數(shù)。這些參數(shù)中,比較容易引起問題的有corePoolSize, maximumPoolSize, workQueue以及handler:
corePoolSize和maximumPoolSize設(shè)置不當會影響效率,甚至耗盡線程;
workQueue設(shè)置不當容易導致OOM;
handler設(shè)置不當會導致提交任務(wù)時拋出異常。
正確的參數(shù)設(shè)置方式會在下文給出。
線程池的工作順序If fewer than corePoolSize threads are running, the Executor always prefers adding a new thread rather than queuing.
If corePoolSize or more threads are running, the Executor always prefers queuing a request rather than adding a new thread.
If a request cannot be queued, a new thread is created unless this would exceed maximumPoolSize, in which case, the task will be rejected.
corePoolSize -> 任務(wù)隊列 -> maximumPoolSize -> 拒絕策略
Runnable和Callable可以向線程池提交的任務(wù)有兩種:Runnable和Callable,二者的區(qū)別如下:
方法簽名不同,void Runnable.run(), V Callable.call() throws Exception 是否允許有返回值,Callable允許有返回值 是否允許拋出異常,Callable允許拋出異常。
Callable是JDK1.5時加入的接口,作為Runnable的一種補充,允許有返回值,允許拋出異常。
三種提交任務(wù)的方式: 如何正確使用線程池 避免使用無界隊列不要使用Executors.newXXXThreadPool()快捷方法創(chuàng)建線程池,因為這種方式會使用無界的任務(wù)隊列,為避免OOM,我們應(yīng)該使用ThreadPoolExecutor的構(gòu)造方法手動指定隊列的最大長度:
ExecutorService executorService = new ThreadPoolExecutor(2, 2, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(512), // 使用有界隊列,避免OOM new ThreadPoolExecutor.DiscardPolicy());明確拒絕任務(wù)時的行為
任務(wù)隊列總有占滿的時候,這是再submit()提交新的任務(wù)會怎么樣呢?RejectedExecutionHandler接口為我們提供了控制方式,接口定義如下:
public interface RejectedExecutionHandler { void rejectedExecution(Runnable r, ThreadPoolExecutor executor); }
線程池給我們提供了幾種常見的拒絕策略:
線程池默認的拒絕行為是AbortPolicy,也就是拋出RejectedExecutionHandler異常,該異常是非受檢異常,很容易忘記捕獲。如果不關(guān)心任務(wù)被拒絕的事件,可以將拒絕策略設(shè)置成DiscardPolicy,這樣多余的任務(wù)會悄悄的被忽略。
ExecutorService executorService = new ThreadPoolExecutor(2, 2, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(512), new ThreadPoolExecutor.DiscardPolicy());// 指定拒絕策略獲取處理結(jié)果和異常
線程池的處理結(jié)果、以及處理過程中的異常都被包裝到Future中,并在調(diào)用Future.get()方法時獲取,執(zhí)行過程中的異常會被包裝成ExecutionException,submit()方法本身不會傳遞結(jié)果和任務(wù)執(zhí)行過程中的異常。獲取執(zhí)行結(jié)果的代碼可以這樣寫:
ExecutorService executorService = Executors.newFixedThreadPool(4); Future
上述代碼輸出類似如下:
線程池的常用場景正確構(gòu)造線程池
int poolSize = Runtime.getRuntime().availableProcessors() * 2; BlockingQueue獲取單個結(jié)果queue = new ArrayBlockingQueue<>(512); RejectedExecutionHandler policy = new ThreadPoolExecutor.DiscardPolicy(); executorService = new ThreadPoolExecutor(poolSize, poolSize, 0, TimeUnit.SECONDS, queue, policy);
過submit()向線程池提交任務(wù)后會返回一個Future,調(diào)用V Future.get()方法能夠阻塞等待執(zhí)行結(jié)果,V get(long timeout, TimeUnit unit)方法可以指定等待的超時時間。
獲取多個結(jié)果如果向線程池提交了多個任務(wù),要獲取這些任務(wù)的執(zhí)行結(jié)果,可以依次調(diào)用Future.get()獲得。但對于這種場景,我們更應(yīng)該使用ExecutorCompletionService,該類的take()方法總是阻塞等待某一個任務(wù)完成,然后返回該任務(wù)的Future對象。向CompletionService批量提交任務(wù)后,只需調(diào)用相同次數(shù)的CompletionService.take()方法,就能獲取所有任務(wù)的執(zhí)行結(jié)果,獲取順序是任意的,取決于任務(wù)的完成順序:
void solve(Executor executor, Collection單個任務(wù)的超時時間> solvers) throws InterruptedException, ExecutionException { CompletionService ecs = new ExecutorCompletionService (executor);// 構(gòu)造器 for (Callable s : solvers)// 提交所有任務(wù) ecs.submit(s); int n = solvers.size(); for (int i = 0; i < n; ++i) {// 獲取每一個完成的任務(wù) Result r = ecs.take().get(); if (r != null) use(r); } }
V Future.get(long timeout, TimeUnit unit)方法可以指定等待的超時時間,超時未完成會拋出TimeoutException。
多個任務(wù)的超時時間等待多個任務(wù)完成,并設(shè)置最大等待時間,可以通過CountDownLatch完成:
public void testLatch(ExecutorService executorService, List線程池和裝修公司tasks) throws InterruptedException{ CountDownLatch latch = new CountDownLatch(tasks.size()); for(Runnable r : tasks){ executorService.submit(new Runnable() { @Override public void run() { try{ r.run(); }finally { latch.countDown();// countDown } } }); } latch.await(10, TimeUnit.SECONDS); // 指定超時時間 }
以運營一家裝修公司做個比喻。公司在辦公地點等待客戶來提交裝修請求;公司有固定數(shù)量的正式工以維持運轉(zhuǎn);旺季業(yè)務(wù)較多時,新來的客戶請求會被排期,比如接單后告訴用戶一個月后才能開始裝修;當排期太多時,為避免用戶等太久,公司會通過某些渠道(比如人才市場、熟人介紹等)雇傭一些臨時工(注意,招聘臨時工是在排期排滿之后);如果臨時工也忙不過來,公司將決定不再接收新的客戶,直接拒單。
線程池就是程序中的“裝修公司”,代勞各種臟活累活。上面的過程對應(yīng)到線程池上:
// Java線程池的完整構(gòu)造函數(shù) public ThreadPoolExecutor( int corePoolSize, // 正式工數(shù)量 int maximumPoolSize, // 工人數(shù)量上限,包括正式工和臨時工 long keepAliveTime, TimeUnit unit, // 臨時工游手好閑的最長時間,超過這個時間將被解雇 BlockingQueue總結(jié)workQueue, // 排期隊列 ThreadFactory threadFactory, // 招人渠道 RejectedExecutionHandler handler) // 拒單方式
Executors為我們提供了構(gòu)造線程池的便捷方法,對于服務(wù)器程序我們應(yīng)該杜絕使用這些便捷方法,而是直接使用線程池ThreadPoolExecutor的構(gòu)造方法,避免無界隊列可能導致的OOM以及線程個數(shù)限制不當導致的線程數(shù)耗盡等問題。ExecutorCompletionService提供了等待所有任務(wù)執(zhí)行結(jié)束的有效方式,如果要設(shè)置等待的超時時間,則可以通過CountDownLatch完成。
參考ThreadPoolExecutor API Doc
編輯 by--二月的獅子
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/77131.html
摘要:任務(wù)性質(zhì)不同的任務(wù)可以用不同規(guī)模的線程池分開處理。線程池在運行過程中已完成的任務(wù)數(shù)量。如等于線程池的最大大小,則表示線程池曾經(jīng)滿了。線程池的線程數(shù)量。獲取活動的線程數(shù)。通過擴展線程池進行監(jiān)控。框架包括線程池,,,,,,等。 Java線程池 [toc] 什么是線程池 線程池就是有N個子線程共同在運行的線程組合。 舉個容易理解的例子:有個線程組合(即線程池,咱可以比喻為一個公司),里面有3...
摘要:當活動線程核心線程非核心線程達到這個數(shù)值后,后續(xù)任務(wù)將會根據(jù)來進行拒絕策略處理。線程池工作原則當線程池中線程數(shù)量小于則創(chuàng)建線程,并處理請求。當線程池中的數(shù)量等于最大線程數(shù)時默默丟棄不能執(zhí)行的新加任務(wù),不報任何異常。 spring-cache使用記錄 spring-cache的使用記錄,坑點記錄以及采用的解決方案 深入分析 java 線程池的實現(xiàn)原理 在這篇文章中,作者有條不紊的將 ja...
摘要:純分享直接上干貨操作系統(tǒng)并發(fā)支持進程管理內(nèi)存管理文件系統(tǒng)系統(tǒng)進程間通信網(wǎng)絡(luò)通信阻塞隊列數(shù)組有界隊列鏈表無界隊列優(yōu)先級有限無界隊列延時無界隊列同步隊列隊列內(nèi)存模型線程通信機制內(nèi)存共享消息傳遞內(nèi)存模型順序一致性指令重排序原則內(nèi)存語義線程 純分享 , 直接上干貨! 操作系統(tǒng)并發(fā)支持 進程管理內(nèi)存管...
摘要:去美團面試,問到了什么是線程池,如何使用,為什么要用以下做個總結(jié)。二線程池線程池的作用線程池作用就是限制系統(tǒng)中執(zhí)行線程的數(shù)量。真正的線程池接口是。創(chuàng)建固定大小的線程池。此線程池支持定時以及周期性執(zhí)行任務(wù)的需求。 去美團面試,問到了什么是線程池,如何使用,為什么要用,以下做個總結(jié)。關(guān)于線程之前也寫過一篇文章《高級面試題總結(jié)—線程池還能這么玩?》 1、什么是線程池:? java.util...
摘要:去美團面試,問到了什么是線程池,如何使用,為什么要用以下做個總結(jié)。二線程池線程池的作用線程池作用就是限制系統(tǒng)中執(zhí)行線程的數(shù)量。真正的線程池接口是。創(chuàng)建固定大小的線程池。此線程池支持定時以及周期性執(zhí)行任務(wù)的需求。 去美團面試,問到了什么是線程池,如何使用,為什么要用,以下做個總結(jié)。關(guān)于線程之前也寫過一篇文章《高級面試題總結(jié)—線程池還能這么玩?》 1、什么是線程池:? java.util...
閱讀 2706·2021-11-18 10:02
閱讀 3470·2021-09-22 15:50
閱讀 2390·2021-09-06 15:02
閱讀 3607·2019-08-29 16:34
閱讀 1771·2019-08-29 13:49
閱讀 1309·2019-08-29 13:29
閱讀 3682·2019-08-28 18:08
閱讀 3008·2019-08-26 11:52