執行緒池之ThreadPoolExecutor
執行緒池的作業主要是控制運行的執行緒的數量,處理程序中將任務放入佇列,然后在執行緒創建后啟動這些任務,如果執行緒數量超過了最大數量,那么超出數量的執行緒排隊等候,等其他執行緒執行完畢再從佇列中取出任務來執行,
在開發程序中,合理地使用執行緒池能夠帶來3個好處:
- 降低資源消耗,通過重復利用已創建的執行緒降低執行緒創建和銷毀造成的消耗;
- 提高回應速度,當任務到達時,任務可以不需要等到執行緒創建就能立即執行;
- 提高執行緒的可管理性,執行緒是稀缺資源,如果無限制地創建,不僅會消耗系統資源,還會降低系統的穩定性,使用執行緒池可以進行統一分配、調優和監控,
1. 執行緒池實作原理
執行緒池主要處理流程:

ThreadPoolExecutor執行execute()方法的示意圖:

2. 執行緒池的使用
2.1 執行緒池創建
通過ThreadPoolExecutor來創建執行緒池:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
創建執行緒池的引數:
1)corePoolSize:執行緒池的核心執行緒數,定義了最小可以同時運行的執行緒數量,
2)maximumPoolSize:執行緒池的最大執行緒數,方佇列中存放的任務達到佇列容量時,房前可以同時運行的執行緒數量變為最大執行緒數,
3)keepAliveTime:當執行緒池中的執行緒數量大于corePoolSize時,如果沒有新任務提交,核心執行緒外的執行緒不會立即銷毀,而是會等待,直到等待的時間超過了KeepAliveTime才會被回收銷毀,
4)unit:keepAliveTime引數的時間單位,包括DAYS、HOURS、MINUTES、MILLISECONDS等,
5)workQueue:用于保存等待執行任務的阻塞佇列,可以選擇以下集個阻塞佇列:
- ArrayBlockingQueue:是一個基于陣列結構的阻塞佇列,此佇列按FIFO原則對元素進行排序;
- LinkedBlockingQueue:是一個基于鏈表結構的阻塞佇列,此佇列按FIFO排序元素,吞吐量通常高于ArrayBlockingQueue,靜態工廠方法Executors.newFixedThreadPool()使用了這個佇列,
- SynchronousQueue:一個不存盤元素的阻塞佇列,每個插入操作必須等到另一個執行緒呼叫移除操作,否則插入操作一直處于阻塞狀態,吞吐量常高于LinkedBlockingQueue,靜態工廠方法Executors.newCachedThreadPool()使用了這個佇列,
- PriorityBlockingQueue:一個具有優先級的無限阻塞佇列,
6)threadFactory:用于設定創建執行緒的工廠,可以通過工廠給每個創造出來的執行緒設定更有意義的名字,使用開源框架guava提供的ThreadFactoryBuilder可以快速給執行緒池里的執行緒設定有意義的名字:
new ThreadFactoryBuilder().setNameFormat("XX-task-%d").build();
7)handler:飽和策略,若當前同時運行的執行緒數量達到最大執行緒數量并且佇列已經被放滿,ThreadPoolExecutor定義了一些飽和策略:
- ThreadPoolExecutor.AbortPolicy:直接拋出RejectedExecutionException例外來拒絕處理新任務;
- ThreadPoolExecutor.CallerRunsPolicy:只用呼叫者所在的執行緒來運行任務,會降低新任務的提交速度,影響程式的整體性能,
- ThreadPoolExecutor.DiscardPolicy:不處理新任務,直接丟棄掉,
- ThreadPoolExecutor.DiscardOldestPolicy:丟棄佇列中最舊的一個任務(等待最久的任務),執行當前任務,
2.2 向執行緒池提交任務
execute()方法用于向執行緒池提交不需要回傳值的任務,所以無法判斷任務是否被執行緒池執行成功,
executor.execute(new Runnable() {
@Override
public void run() {
// TODO
}
});
submit()方法用于提交需要回傳值的任務,執行緒池會回傳一個future型別的物件,通過這個future物件可以判斷任務是否執行成功,并且可以通過future的get()方法獲取回傳值,get()方法會阻塞當前執行緒直到任務完成,而使用get(long timeout, TimeUnit unit)方法則會阻塞當前執行緒一段時間后立即回傳,這時有可能任務還沒有執行完,
Future<T> future = executor.submit(hasReturnValueTask);
try {
T s = future.get();
} catch (InterruptedExecption | ExecutortionExcception e) {
// 處理例外
e.printStackTrace();
} finally {
// 關閉執行緒池
executor.shutdown();
}
2.3 關閉執行緒池
可以使用執行緒池的shutdown或shutdownNow方法來關閉執行緒池,其原理在于遍歷執行緒池中的作業執行緒,然后逐個呼叫執行緒的interrupt方法來中斷執行緒,所以無法回應中斷的任務可能無法終止,
二者區別在于:shutdownNow方法首先將執行緒池狀態設定為STOP,然后嘗試停止所有正在執行或暫停任務的執行緒,并回傳等到執行任務的串列,而shutdown只是將執行緒池的狀態設定為SHUTDOWN狀態,然后中斷所有沒有整在執行任務的執行緒,
2.4 合理配置執行緒池
查看當前設備的CPU核數:
Runtime.getRuntime().availableProcessors()
-
CPU密集型任務:任務需要大量的運算,而沒有阻塞,CPU一直全速運行,
CPU密集型任務配置盡可能的少的執行緒數量,
公式:CPU核數 + 1個執行緒的執行緒池,
-
IO密集型任務:任務需要大量的IO,即大量的阻塞,
由于IO密集型任務執行緒并不是一直在執行任務,可以多分配一點執行緒數,如CPU核數*2,
公式:CPU核數/(1-阻塞系數),其中阻塞系數在0.8-0.9之間,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/63406.html
標籤:Java
上一篇:openPGP加解密示例
