文章目录
- 资源泄漏(线程未关闭)
- 问题描述
- 错误实现
- 优化原理
- 正确实现
- 优化原理
资源泄漏(线程未关闭)
问题描述
应用程序启动时创建线程池处理任务,但未在应用关闭时正确关闭线程池。
- 现象:
- 应用重启时,残留的线程池线程仍在运行,占用内存和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. 建议
- 统一管理线程池生命周期:在应用启动/关闭时显式初始化和销毁线程池。
- 监控线程池状态:记录活跃线程数、队列大小、拒绝任务数等指标。
- 防御性编程:任务代码需正确处理中断,避免死循环。