你好,我是 shengjk1,多年大廠經驗,努力構建 通俗易懂的、好玩的編程語言教程。 歡迎關注!你會有如下收益:
- 了解大廠經驗
- 擁有和大廠相匹配的技術等
希望看什么,評論或者私信告訴我!
文章目錄
- 一、前言
- 二、線程池
- 2.1 線程池是什么?
- 2.2 線程池優勢
- 2.3 線程池的分類
- 2.4 線程池的使用
- 2.4.1 ThreadPoolExecutor 創建
- 2.4.2 向線程池提交任務
- 2.4.3 線程池關閉
- 2.4.4 完整代碼樣例
- 2.5 如何合理的配置線程池
- 2.6 線程池監控
- 2.7 其他
- 三、擴展
- 四、總結
一、前言
前面我們幾章我們一起聊了java多線程的一些基本知識以及java多線程間的通信。但往往在多線程開發中使用最多的不是這些原生的東西,而是線程池,幾乎所有需要異步或并發執行任務的程序都可以使用線程池。今天我們就一起聊一下java線程池
二、線程池
2.1 線程池是什么?
顧名思義,線程池就是線程的池子,用來管理和復用線程。它可以在應用程序中有效地管理線程的生命周期、調度和執行。
線程池包含一組預先創建的線程,這些線程可以被動態分配給執行任務,從而避免了不斷創建和銷毀線程的開銷。線程池通常用于執行大量的異步任務,提高多線程程序的性能和穩定性。
2.2 線程池優勢
- 降低資源消耗:通過重復利用已創建的線程降低線程創建和銷毀造成的消耗。
- 提高響應速度:當任務到達時,任務可以不需要等到線程創建就能立即執行。
- 提高線程的可管理性:線程是稀缺資源,如果無限制地創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一分配、調優和監控。
2.3 線程池的分類
在 Java 中,線程池主要分為以下幾類:
- FixedThreadPool(固定大小線程池):固定大小線程池中包含固定數量的線程,當有任務提交時,線程池中沒有空閑線程時,任務會被放入任務隊列中等待執行。這種線程池適用于需要限制并發線程數量的場景。
- CachedThreadPool(緩存線程池):緩存線程池不固定線程數量,可以根據需要自動創建新線程,當線程空閑時間超過設定的時間(默認60s)會被回收,適用于短期異步任務。
- SingleThreadPool(單線程池):單線程池只包含一個工作線程,保證所有任務按順序執行。適用于需要保持任務順序執行的場景。
- ScheduledThreadPool(定時線程池):這種線程池可以執行定時任務和周期性任務。它繼承自ExecutorService,并通過 ScheduledExecutorService 接口提供定時任務功能。
- WorkStealingPool(工作竊取線程池):這是Java 8中引入的一種新類型的線程池,主要用于處理耗時任務。它適用于需要大量并行任務、任務之間沒有依賴關系的情況。
- CustomThreadPool(自定義線程池):除了以上內置的線程池類型,開發人員也可以根據實際需求自定義線程池,通過配置線程池的參數來適應特定的業務場景。 這些線程池類型允許開發人員根據不同的場景和需求選擇適合的線程池,從而提高多線程程序的性能和效率。在實際應用中,根據任務的特性和線程池的負載情況選擇適當的線程池類型非常重要。
2.4 線程池的使用
線程池分類中前4個基本上都是通過 ThreadPoolExecutor
來實現的,所以我們以 ThreadPoolExecutor 為例
2.4.1 ThreadPoolExecutor 創建
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)
ThreadPoolExecutor
構造函數包含多個參數,每個參數都影響著線程池的行為。下面解釋每個參數的作用以及如何使用:
corePoolSize
:核心線程池大小,當提交一個任務到線程池時,線程池會創建一個線程來執行任務,即使其他空閑的核心線程能夠執行新任務也會創建線程,等到需要執行的任務數大于核心線程池大小時就不再創建。如果調用了線程池的prestartAllCoreThreads()方法,線程池會提前創建并啟動所有核心線程
- 作用:線程池中保持的最小線程數,即使線程是空閑的。
- 如何使用:這些線程在處理任務時不會被回收,除非設置了
allowCoreThreadTimeOut(true)
來允許核心線程在空閑時被回收。
maximumPoolSize
:線程池的最大線程數
- 作用:線程池允許的最大線程數,包括核心線程和非核心線程。
- 如何使用:當任務隊列已滿且活動線程數小于最大線程數時,會創建新的線程。
keepAliveTime
:線程空閑時間
- 作用:非核心線程的空閑時間超過這個值就會被回收。
- 如何使用:指定非核心線程空閑線程的最長存活時間。
unit
:時間單位
- 作用:用于指定
keepAliveTime
的時間單位,可以是TimeUnit.MILLISECONDS
等。 - 如何使用:配合
keepAliveTime
使用。
workQueue
:任務隊列
- 作用:用于保存等待執行的任務。 值得注意的是,如果使用了無界的任務隊列這個參數就沒什么效果
- 如何使用:選擇合適的任務隊列,例如
LinkedBlockingQueue
、ArrayBlockingQueue
等,以滿足你的需求。
threadFactory
:線程工廠
- 作用:用于創建新線程。 用于設置創建線程的工廠,可以通過線程工廠給每個創建出來的線程設置更有意義的名字
handler
:拒絕策略(RejectedExecutionHandler)
- 作用:當隊列和線程池都滿了,說明線程池處于飽和狀態,那么必須采取一種策略處理提交的新任務。這個策略默認情況下是AbortPolicy,表示無法處理新任務時拋出異常
- 如何使用:可以選擇使用預設的策略,如
ThreadPoolExecutor.AbortPolicy
(任務被拒絕時拋出異常)、ThreadPoolExecutor.CallerRunsPolicy
(只用調用者所在線程來運行任務)、ThreadPoolExecutor.DiscardPolicy
(丟棄隊列里最近的一個任務,并執行當前任務)、DiscardPolicy
(不處理,丟棄掉),或者也可以根據應用場景需要來實現RejectedExecutionHandler
接口自定義策略。如記錄日志或持久化存儲不能處理的任務。
通過合理設置這些參數,可以根據具體場景和需求創建一個適合的線程池,提高程序的性能和效率。要根據具體的使用情況和需求來調整這些參數,以獲得最佳的線程池性能。
2.4.2 向線程池提交任務
可以使用兩個方法向線程池提交任務,分別為execute()和submit()方法
- execute()方法用于提交不需要返回值的任務,所以無法判斷任務是否被線程池執行成功。
executor.execute(() -> {System.out.println(Thread.currentThread().getName() + " is running...");
});
- submit()方法用于提交需要返回值的任務。線程池會返回一個future類型的對象,通過這個future對象可以判斷任務是否執行成功,并且可以通過future的get()方法來獲取返回值,get()方法會阻塞當前線程直到任務完成,而使用get(long timeout,TimeUnit unit)方法則會阻塞當前線程一段時間后立即返回,這時候有可能任務沒有執行完。
Future<String> future = executor.submit(() -> {System.out.println("Task is running on thread: " + Thread.currentThread().getName());return "Task Result";
});
try {// 獲取任務的執行結果String result = future.get();System.out.println("Task result: " + result);
} catch (InterruptedException | ExecutionException e) {e.printStackTrace();
}
2.4.3 線程池關閉
可以通過調用線程池的shutdown或shutdownNow方法來關閉線程池。
它們的原理是遍歷線程池中的工作線程,然后逐個調用線程的interrupt方法來中斷線程,所以無法響應中斷的任務可能永遠無法終止。
但是它們存在一定的區別,
- shutdownNow將嘗試立即關閉線程池,首先將線程池的狀態設置成STOP,然后嘗試停止所有的正在執行或暫停任務的線程,并返回等待執行任務的列表,
- shutdown只是將線程池的狀態設置成SHUTDOWN狀態,然后中斷所有沒有正在執行任務的線程,等待所有正在執行的任務完成。
只要調用了這兩個關閉方法中的任意一個,isShutdown方法就會返回true。當所有的任務都已關閉后,才表示線程池關閉成功,這時調用isTerminaed方法會返回true。至于應該調用哪一種方法來關閉線程池,應該由提交到線程池的任務特性決定,通常調用shutdown方法來關閉線程池,如果任務不一定要執行完,則可以調用shutdownNow方法
2.4.4 完整代碼樣例
public class CustomThreadPoolExecutorExample {public static void main(String[] args) {// 自定義線程工廠ThreadFactory customThreadFactory = new CustomThreadFactory("CustomThread");// 創建 ThreadPoolExecutor,傳入自定義的 ThreadFactoryThreadPoolExecutor executor = new ThreadPoolExecutor(2, // 核心線程數5, // 最大線程數10, // 線程空閑時間TimeUnit.SECONDS, // 時間單位new ArrayBlockingQueue<>(10), // 任務隊列customThreadFactory, // 自定義 ThreadFactorynew ThreadPoolExecutor.AbortPolicy() // 拒絕策略);// 提交任務給線程池// 提交任務并獲取 Future 對象Future<String> future = executor.submit(() -> {System.out.println("Task is running on thread: " + Thread.currentThread().getName());return "Task Result";});try {// 獲取任務的執行結果String result = future.get();System.out.println("Task result: " + result);} catch (InterruptedException | ExecutionException e) {e.printStackTrace();}executor.execute(() -> {System.out.println(Thread.currentThread().getName() + " is running...");});// 關閉線程池executor.shutdown();// 等待線程池關閉try {if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {executor.shutdownNow();if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {System.err.println("線程池無法正常關閉");}}} catch (InterruptedException e) {executor.shutdownNow();Thread.currentThread().interrupt();}}// 自定義線程工廠實現static class CustomThreadFactory implements ThreadFactory {private final String threadNamePrefix;public CustomThreadFactory(String threadNamePrefix) {this.threadNamePrefix = threadNamePrefix;}@Overridepublic Thread newThread(Runnable r) {Thread thread = new Thread(r);thread.setName(threadNamePrefix + "-" + thread.getId());thread.setPriority(Thread.MAX_PRIORITY);return thread;}}
}
2.5 如何合理的配置線程池
根據任務特征來配置線程池是非常重要的,不同類型的任務可能對線程池的需求有所不同。以下是根據任務特征來配置合理線程池的一些建議:
- CPU密集型任務: - 對于大量的 CPU 密集型任務,應該配置較小的線程池,以充分利用 CPU 資源,避免線程之間頻繁切換的開銷。 - 可以采用固定大小的線程池,核心線程數設置為處理器數量,避免創建過多的線程。
- IO密集型任務: - 對于涉及大量 IO 操作的任務,應該配置較大的線程池,以充分利用線程阻塞時間,提高系統整體的 IO 效率。 - 可以考慮使用帶有隊列的緩沖線程池,避免線程池中的線程過度增長。
- 長時間阻塞任務: - 針對長時間阻塞的任務,不宜使用較小的核心線程數,以免影響其他任務的執行。 比如:依賴數據庫連接池的任務,因為線程提交SQL后需要等待數據庫返回結果,等待的時間越長,則CPU空閑時間就越長,那么線程數應該設置得越大,這樣才能更好地利用CPU
- 短時任務: - 對于大量短時任務,可以使用緩存線程池,避免頻繁創建和銷毀線程,提高性能。 - 使用具有自動回收機制的線程池,可以根據需求動態調整線程池大小。
- 定時任務: - 對于定時任務,應該考慮使用支持定時任務執行的線程池,如
ScheduledThreadPoolExecutor
。 - 設置合適的核心線程數和最大線程數,避免因任務過多導致調度延遲。
總的來說,根據任務特征來配置線程池需要結合任務的特點和系統的實際情況,合理選擇核心線程數、最大線程數、任務隊列類型、存活時間等參數,以提高系統性能、資源利用率和穩定性。不斷監控線程池的運行情況,并根據實際需求及時調整配置,是保證線程池運行效率的關鍵。
2.6 線程池監控
可以通過線程池提供的參數進行監控,在監控線程池的時候可以使用以下屬性。
- taskCount:線程池需要執行的任務數量。
- completedTaskCount:線程池在運行過程中已完成的任務數量,小于或等于taskCount。
- largestPoolSize:線程池里曾經創建過的最大線程數量。通過這個數據可以知道線程池是否曾經滿過。如該數值等于線程池的最大大小,則表示線程池曾經滿過。
- getPoolSize:線程池的線程數量。如果線程池不銷毀的話,線程池里的線程不會自動銷毀,所以這個大小只增不減。
- getActiveCount:獲取活動的線程數。
-擴展線程池進行監控。可以通過繼承線程池來自定義線程池,重寫線程池的beforeExecute、afterExecute和terminated方法,也可以在任務執行前、執行后和線程池關閉前執行一些代碼來進行監控。例如,監控任務的平均執行時間、最大執行時間和最小執行時間等。這幾個方法在線程池里是空方法。
2.7 其他
建議使用有界隊列。有界隊列能增加系統的穩定性和預警能力,可以根據需要設大一點兒
三、擴展
在HotSpot VM的線程模型中,Java線程(java.lang.Thread)被一對一映射為本地操作系統線程。Java線程啟動時會創建一個本地操作系統線程;當該Java線程終止時,這個操作系統線程也會被回收。操作系統會調度所有線程并將它們分配給可用的CPU
四、總結
本文為Java多線程編程提供了全面的線程池指南,從基本概念到具體實現,再到優化配置和監控,幫助讀者深入理解并有效運用線程池,以提升并發編程的性能和效率。