文章目錄
- 資源泄漏(線程未關閉)
- 問題描述
- 錯誤實現
- 優化原理
- 正確實現
- 優化原理
資源泄漏(線程未關閉)
問題描述
應用程序啟動時創建線程池處理任務,但未在應用關閉時正確關閉線程池。
- 現象:
- 應用重啟時,殘留的線程池線程仍在運行,占用內存和CPU資源。
- 若線程池任務涉及外部資源(如數據庫連接),可能導致資源耗盡或端口占用。
錯誤實現
- 案例:未正確關閉線程池
public class ResourceLeakDemo {private static final ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 4, 60, TimeUnit.SECONDS,new LinkedBlockingQueue<>());public static void main(String[] args) {executor.execute(() -> {while (true) {try {System.out.println("執行任務中...");Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});// 模擬應用關閉(未關閉線程池)System.out.println("主線程結束,但線程池仍在運行!");}
}
- 結果
主線程結束,但線程池仍在運行!
執行任務中...
執行任務中...
...
- 主線程結束后,線程池中的核心線程(非守護線程)會持續運行,導致JVM無法退出。
優化原理
調優方案與場景
調優目標
- 確保線程池關閉:在應用退出時,優雅關閉線程池,釋放所有資源。
- 防止資源泄漏:終止所有線程,避免殘留任務占用系統資源。
解決方案
- 顯式調用
shutdown()
:在應用退出邏輯中手動關閉線程池。 - 注冊JVM關閉鉤子:確保即使非正常退出(如kill命令),也能觸發線程池關閉。
正確實現
- 調優后:
public class ResourceLeakFixedDemo {private static final ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 4, 60, TimeUnit.SECONDS,new LinkedBlockingQueue<>());public static void main(String[] args) {// 注冊JVM關閉鉤子Runtime.getRuntime().addShutdownHook(new Thread(() -> {System.out.println("JVM關閉鉤子觸發:關閉線程池...");shutdownThreadPool();}));executor.execute(() -> {while (true) {try {System.out.println("執行任務中...");Thread.sleep(1000);} catch (InterruptedException e) {System.out.println("任務被中斷,退出循環");break;}}});// 模擬正常關閉(手動調用關閉邏輯)shutdownThreadPool();System.out.println("應用主線程結束");}private static void shutdownThreadPool() {executor.shutdown(); // 停止接受新任務,等待已有任務完成try {// 等待任務終止,最多10秒if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {executor.shutdownNow(); // 強制終止所有任務System.out.println("線程池強制關閉");}} catch (InterruptedException e) {executor.shutdownNow();}}
}
- 運行后:
執行任務中...
執行任務中...
JVM關閉鉤子觸發:關閉線程池...
執行任務中...
執行任務中...
執行任務中...
執行任務中...
線程池強制關閉
應用主線程結束
任務被中斷,退出循環
優化原理
關鍵調優總結
1、注冊關閉鉤子:
Runtime.getRuntime().addShutdownHook(new Thread(() -> shutdownThreadPool()));
2、優雅關閉線程池:
shutdown()
:停止接受新任務,等待已有任務完成。awaitTermination()
:設定超時時間,避免無限等待。shutdownNow()
:超時后強制終止所有任務(向線程發送中斷信號)。
3、任務響應中斷:
try {Thread.sleep(1000);
} catch (InterruptedException e) {break; // 捕獲中斷信號,退出任務
}
4. 建議
- 統一管理線程池生命周期:在應用啟動/關閉時顯式初始化和銷毀線程池。
- 監控線程池狀態:記錄活躍線程數、隊列大小、拒絕任務數等指標。
- 防御性編程:任務代碼需正確處理中斷,避免死循環。