第9章 Java中的線程池
線程池的優點
【1】降低資源消耗。通過重複利用已創建的線程,可以減少創建、銷燬線程的消耗
【2】提高響應速度。可以直接使用已創建線程。
【3】提高線程的管理性。通過線程池統一的分配、調優和監控。
線程池創建
new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue) 最少參數
{this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler)最多參數
int corePoolSize 線程池的基本的大小,當提交一個任務到線程池,線程池會創建一個線程來執行任務。即使有空閒線程也會創建新的線程。線程數到達corePoolSize大小時,就不在創建新的。如果使用線程池的prestartAllCoreThreads()方法,線程池會提前創建並啓動所有基本線程。
int maximumPoolSize 線程池最大數量,線程池允許創建的最大數量。如果任務隊列已經滿了,並且已創建的線程數小於maximumPoolSize,線程池會再創建新的線程。如果阻塞隊列是無界隊列,這個參數就沒軟用了。
long keepAliveTime 線程保持活動時間,工作線程空閒後保持的存活時間。
TimeUnit unit 線程保持活動時間的單位。可選DAYS天、HOURS小時、MINUTES分鐘、MILLISECOUNDS毫秒
BlockingQueue<Runnable> workQueue 用於保存等待執行的任務的阻塞隊列。可以選擇ArrayBlockingQueue、LinkedBlockingQueue等
ThreadFactory threadFactory 用於設置創建線程的工廠,可以通過線程工廠爲每個線程設置有意義的名字。使用了開源框架的guava提供的ThreadFactoryBuilder。
RejectedExecutionHandler handler 飽和策略。當隊列和線程池飽和的情況下,採用的提交策略。默認是AbortPolicy
==========================================================================================================================
----------
提交任務:submit和execute
可以通過submit()和execute()方法提交任務。
區別:
【1】execute()是在Executor接口中定義,也是Executor中唯一的抽象方法。submit()是在ExecutorService中定義的(繼承了Executor接口)
【2】execute()方法沒有返回值,submit()返回一個Future對象。通過Future.get可以判斷任務是否執行成功、或者執行完成。(主要區別)
【3】submit()方便主線程對Exception做處理。通過Future.get捕獲拋出異常。
注意:Future.get方法是阻塞的。
void execute(Runnable command) :
在將來某個時間執行給定任務。可以在新線程中或者在現有池線程中執行該任務。如果無法將任務提交執行,或者因爲此執行程序已關閉,或者因爲已達到其容量,則該任務由當前 RejectedExecutionHandler處理。
submit被重載三次
Future<?> submit(Runnable task) 提交一個 Runnable 任務用於執行,並返回一個表示該任務的 Future。該 Future 的 get 方法在成功完成時將會返回null。如果拋出異常,get方法將捕獲異常。
Future submit(Runnable task,T result) Runnable 任務用於執行,並返回一個表示該任務的 Future。該 Future 的 get 方法在成功完成時將會返回給定的結果(也就是預先傳遞的result參數)。如果拋出異常,get方法將捕獲異常。
submit(task,null)和submit(task)是一樣的。
Future submit(Callable task) 提交一個返回值的任務用於執行,返回一個表示任務的未決結果的 Future。該 Future 的 get 方法在成功完成時將會返回該任務的結果。 如果想立即阻塞任務的等待,則可以使用 result = exec.submit(aCallable).get();
public static void main(String[] args) throws InterruptedException, ExecutionException {
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>();
ThreadPoolExecutor e= new ThreadPoolExecutor(20, 50, 1L, TimeUnit.MINUTES, workQueue);
// Future<?> f = e.submit(new MyRunnable(),new Re());
Future<String> f = e.submit(new MyCallable());
System.out.println("future get : "+ f.get());
}
static class MyRunnable implements Runnable{
public void run() {
String name = "mm";
System.out.println(name);
throw new RuntimeException("haha");
}
}
static class MyCallable implements Callable<String>{
public String call() throws Exception {
String name = "dd";
System.out.println(name);
Thread.sleep(5000);
return name;
}
}
static class Re{
String re = "ok";
}
【1】execute方法性能較高,所以不需要返回值的時候儘量可以選用。
【2】如果需要返回值,可以實現Callable接口。如果只是需要得到空的結果使用submit就好。
【3】Callable和Future使用可以實現異步執行方法,異步獲取執行結果。
關閉線程池
通過調用shutdown或者shutdownNow方法來關閉線程池。原理是遍歷線程池中的工作線程,然後逐個調用interrupt方法來中斷線程,所以無法響應中斷的線程可能永遠不能停止。
shutdownNow:將線程池狀態設置爲STOP,停止所有正在執行或空閒的線程,並返回等待執行任務的列表。
shutdown:只是將線程狀態設置爲SHUTDOWN,然後終端空閒的線程。
調用了以上的任意一個,isShutdown都會返回true。當所有任務都關閉以後纔算線程池關閉成功,isTerminated方法才返回true。通常調用shutdown方法,如果任務不執行完也行,也可以使用shutdownNow。
合理配置線程池
1.判斷任務性質:CPU密集型、IO密集型、混合型
2.可以選擇使用優先級隊列處理優先級不同的任務。(注意:可以導致低優先級的任務永遠不執行)
3.建議使用有界隊列。可以增加系統的穩定性和預警能力,維持在一定的任務數量,可以設置爲幾千。
線程池的監控
在系統中使用了大量線程池,有必要對線程池進行監控,可以根據線程池使用情況快速定位問題。
taskCount:線程需要執行的任務數量
completedTaskCount:線程池已完成的任務書,小於等於taskCount
largestPoolSize:線程池曾經創建的最大線程數。可以用來判斷線程池是否滿過。
getPoolSize:線程池的線程數量。線程池不銷燬的話,線程池中的線程不會自動銷燬,所以這個數值只增不減。
還可以對線程池進行擴展。通過集成線程池,重寫線程池的beforeExecute、afterExecute和terminated方法。執行一些自定義的代碼來監控。
例如監控任務的平均時間,最大(最小)執行時間等。在線程池中默認是空方法。