線程池提供了一種任務的提交與任務的執行解偶的策略,而且有如下幾種優點java
提升資源利用率
經過複用本身的線程來下降建立線程和銷燬線程的開銷。
若是不用線程池而採用爲每一個任務都建立一個新線程的話會很浪費系統資源。由於建立線程和銷燬線程都是耗系統資源的行爲。除此以外還會因爲線程過多而致使JVM出現OutOfMemory數組
提升響應速度
當新來一個任務時,若是有空閒線程存在可當即執行任務,中間節省了建立線程的過程緩存
統一管理線程
若是不用線程池來管理,而是無限建立線程的話不只消耗系統資源,並且還會致使系統不穩定。使用線程池能夠進行統一分配,調優以及監控。bash
經過Executors的工廠方法來建立線程池,好比建立一個固定線程數的線程池多線程
ExecutorService executorService = Executors.newFixedThreadPool(5);
複製代碼
經過ThreadPoolExecutor的構造函數來建立線程池ide
ThreadPoolExecutor pool = new ThreadPoolExecutor(int corePoolSize,
int maxmumPoolSize,
long keepAliveTime,
TimeUnit unit,
BolockingQueue<Runnable> workQueue,
ThreadFactory factory,
RejectedExecusionHandler handler);
複製代碼
官方比較推薦使用後者,由於能夠靈活選擇參數配置,以及自定義配置函數
不管是採用那種方式建立一個線程池,它內部都是經過 ThreadPoolExecutor的構造函數來實現的。因此要想全面掌握線程池的各類特性以及性能的話須要對 ThreadPoolExecutor類深刻理解,包括各類參數的意義,參數之間是如何配合工做的等等。性能
corePoolSize 核心線程數。默認狀況下,當提交一個任務時線程池會新建一個線程池(及時有空閒線程存在),直到線程數量等於基本大小(也能夠預初始化線程)ui
workQueue 一個存聽任務的阻塞隊列,當線程池裏的基本線程都在忙着的時候,提交一個新任務的話會暫時存放到阻塞隊列,等待執行,經常使用的阻塞隊列有一下幾種this
maximumPoolSize 最大線程數。當全部的核心線程都在運行而且阻塞隊列也滿了的話會建立額外的幾個線程來執行,這時候線程池裏的全部線程數量就是最大線程數量。若是線程池採用的是像LinkedBlockingQueue這種無界隊列的話,該參數不會起到做用
KeepAliveTime 和 TimeUnit 若是某個線程的空閒時間大於KeepAliveTime的時候會被標記爲可回收, 而且當前線程池裏的數量大於核心線程數量的時候會被終止。因此該參數跟maximumPoolSize同樣,使用無界隊列的時候不起做用,TimeUnit是時間單位
RejectedExecusionHandler 飽和策略。若是咱們的任務隊列滿了,而且線程池裏的線程數量已經達到了最大線程數,並且這些線程都再也不空閒狀態。這時候新提交任務的話,沒法去執行,因此須要一種飽和策略來去處理這些任務。Java提供了一下幾種策略
Java類庫提供了靈活的建立線程池方法,能夠經過調用Executors中的靜態工廠方法來建立一個線程池。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
複製代碼
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
複製代碼
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
複製代碼
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
複製代碼
線程池執行任務是由Executor接口的execute()方法來執行的,下面看下執行任務流程的一段核心代碼(java8)
public void execute(Runnable command) {
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
複製代碼
線程池的執行流程由如下幾個步驟來完成(每種線程池的實現略有不一樣,但核心思想類似)
1)首先,若是當前工做的線程數量小於核心線程數,則新建立一個線程來執行。若是核心線程池數滿了,
2)判斷線程池的任務隊列是否已滿,若是沒滿的話把該任務放到任務隊列裏,等待覈心線程空閒以後去執行,若是滿了的話進入下一階段
3)判斷判斷線程池裏工做的線程數量是否達到了最大線程數,若是沒達到則建立一個新的線程來去執行。不然,採用飽和策略來處理
若是進去看內部實現的話會發現,線程池會把每一個工做線程包裝成Worker,而把須要執行的任務包裝成Task來運行。
在項目中使用線程池的時候有必要對線程池進行監控,這樣能夠根據線程池的使用情況快速定位問題。能夠經過線程池提供的參數進行監控,在監控線程池的時候可使用如下屬性。
示例代碼
public class Task implements Runnable{
private int i;
public Task(int i) {
this.i = i;
}
@Override
public void run() {
System.out.println("task num ="+i);
}
}
複製代碼
public class Test {
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(10,20,60,TimeUnit.SECONDS,new ArrayBlockingQueue<>(5));
for (int i=0;i<5;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("完成任務數= "+pool.getCompletedTaskCount()+" 任務數= "+pool.getTaskCount()+" " +
""+" 活動線程數="+pool.getActiveCount() +"" +" 線程數="+pool.getPoolSize() +" "+" 最大線程數="+pool.getLargestPoolSize() );
Task task = new Task(i);
pool.execute(task);
}
pool.shutdown();
}
}
複製代碼
打印結果以下:
完成任務數= 0 任務數= 0 活動線程數=0 線程數=0 最大線程數=0
task num =0
完成任務數= 1 任務數= 1 活動線程數=0 線程數=1 最大線程數=1
task num =1
完成任務數= 2 任務數= 2 活動線程數=0 線程數=2 最大線程數=2
task num =2
完成任務數= 3 任務數= 3 活動線程數=0 線程數=3 最大線程數=3
task num =3
完成任務數= 4 任務數= 4 活動線程數=0 線程數=4 最大線程數=4
task num =4
複製代碼