线程池
Java线程池详解
线程池概念与优势
线程池是一种线程使用模式,它预先创建一组线程,并通过管理这些线程来执行任务,避免了频繁创建和销毁线程的开销。主要优势包括:
- 降低资源消耗,复用线程避免创建/销毁开销
- 提高响应速度,任务到达时可立即执行
- 提高线程的可管理性,统一分配、调优和监控
- 防止系统因线程过多而导致的资源耗尽
ThreadPoolExecutor的七大核心参数
- corePoolSize(核心线程数)
-
- 线程池保持的最小线程数
- 即使这些线程处于空闲状态也不会被回收(除非设置了allowCoreThreadTimeOut)
- 线程池创建后,可以预热启动所有核心线程
- maximumPoolSize(最大线程数)
-
- 线程池允许创建的最大线程数
- 当工作队列已满且活动线程数量达到corePoolSize后,新提交的任务会创建新线程直到达到此上限
- keepAliveTime(线程存活时间)
-
- 当线程数超过核心线程数时,多余的空闲线程在终止前等待新任务的最长时间
- 如果设置allowCoreThreadTimeOut为true,核心线程也会受此参数影响
- unit(时间单位)
-
- keepAliveTime的时间单位
- 如TimeUnit.SECONDS, TimeUnit.MILLISECONDS等
- workQueue(工作队列)
-
- 用于存放待执行任务的阻塞队列
- 常见队列类型:
-
-
- ArrayBlockingQueue:有界队列,FIFO
- LinkedBlockingQueue:可选有界或无界队列,FIFO
- SynchronousQueue:不存储元素的队列,每个插入操作必须等待另一个线程调用移除操作
- PriorityBlockingQueue:优先级队列,允许任务按优先级执行
-
任务优先级是通过让任务类实现Comparable接口或提供Comparator来确定的。线程池中的线程会按照队列确定的优先级顺序来获取和执行任务,但线程本身的优先级是相同的。
使用PriorityBlockingQueue的例子:
java
class PriorityTask implements Runnable, Comparable<PriorityTask> {
private final int priority;
private final String name;
public PriorityTask(int priority, String name) {
this.priority = priority;
this.name = name;
}
@Override
public void run() {
System.out.println("执行任务: " + name + ", 优先级: " + priority);
}
@Override
public int compareTo(PriorityTask other) {
// 数字越小优先级越高
return Integer.compare(this.priority, other.priority);
}
}
// 使用优先级队列的线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
3, 3, 0L, TimeUnit.MILLISECONDS,
new PriorityBlockingQueue<>()
);
// 提交不同优先级的任务
executor.execute(new PriorityTask(10, "低优先级任务"));
executor.execute(new PriorityTask(5, "中优先级任务"));
executor.execute(new PriorityTask(1, "高优先级任务"));
- threadFactory(线程工厂)
-
- 用于创建新线程的工厂
- 可以自定义线程名称、优先级、守护状态等
- 默认工厂创建的线程命名为"pool-N-thread-M",为非守护线程,优先级为NORM_PRIORITY
- handler(拒绝策略)
-
- 当线程池和队列都满了,新提交任务的处理策略
- 内置四种拒绝策略:
-
-
- AbortPolicy:默认策略,抛出RejectedExecutionException异常
- CallerRunsPolicy:在调用者线程中执行任务
- DiscardPolicy:静默丢弃新任务
- DiscardOldestPolicy:丢弃队列中最早的任务,然后尝试重新提交新任务
-
线程池工作流程
- 如果运行的线程数少于corePoolSize,线程池会创建新线程来处理请求
- 如果线程数大于或等于corePoolSize,任务会被放入工作队列
- 如果工作队列已满,且运行的线程数少于maximumPoolSize,线程池会创建新线程来处理请求
- 如果工作队列已满,且运行的线程数大于或等于maximumPoolSize,拒绝策略会被触发
常见线程池类型
所谓的"线程池类型"实际上指的是不同配置参数组合形成的线程池使用模式。JDK通过Executors
工厂类提供了几种预定义的线程池配置,这些只是对ThreadPoolExecutor
不同参数组合的封装,主要包括
- FixedThreadPool
-
- 固定大小线程池,corePoolSize与maximumPoolSize相同
- 使用无界LinkedBlockingQueue作为工作队列
- 适用于负载稳定的场景
- CachedThreadPool
-
- 可缓存线程池,corePoolSize为0,maximumPoolSize为Integer.MAX_VALUE
- 使用SynchronousQueue作为工作队列
- 空闲线程保留60秒,适用于执行大量短期异步任务
- SingleThreadExecutor
-
- 单线程线程池,corePoolSize和maximumPoolSize均为1
- 使用无界LinkedBlockingQueue作为工作队列
- 确保任务按提交顺序串行执行
- ScheduledThreadPoolExecutor
-
- 支持定时及周期性任务执行
- 使用DelayedWorkQueue作为工作队列
- 可以设置固定延迟(scheduleWithFixedDelay)或固定速率(scheduleAtFixedRate)执行任务
线程池使用实例
// 创建自定义线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, // 核心线程数
10, // 最大线程数
60, TimeUnit.SECONDS, // 空闲线程存活时间
new LinkedBlockingQueue<>(100), // 工作队列
new CustomThreadFactory("业务处理线程"), // 自定义线程工厂
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
// 提交任务
executor.execute(() -> {
System.out.println("执行任务...");
});
// 提交有返回值的任务
Future<String> future = executor.submit(() -> {
// 执行任务
return "任务执行结果";
});
// 获取任务结果(阻塞)
try {
String result = future.get();
} catch (InterruptedException | ExecutionException e) {
// 处理异常
}
// 关闭线程池(不再接受新任务,已提交的任务会执行完)
executor.shutdown();
// 立即关闭线程池(尝试停止所有正在执行的任务)
executor.shutdownNow();
线程池监控与调优
- 核心监控指标
-
- 活跃线程数:getActiveCount()
- 完成任务数:getCompletedTaskCount()
- 队列大小:getQueue().size()
- 线程池大小:getPoolSize()
- 最大线程数:getLargestPoolSize()
- 线程池调优原则
-
- CPU密集型任务:线程数 = CPU核心数 + 1
- IO密集型任务:线程数 = CPU核心数 * (1 + IO耗时/CPU耗时)
- 混合型任务:需要根据实际性能测试进行调整
- 避免使用无界队列,防止OOM
- 根据业务波峰设置合理的maximumPoolSize
- 根据任务执行时间设置合理的keepAliveTime
线程池最佳实践
- 使用ThreadPoolExecutor构造方法创建线程池,避免使用Executors工厂方法(可能导致资源耗尽)
- 为线程池中的线程指定有意义的名称,方便问题排查
- 设置合理的拒绝策略并进行异常处理
- 根据任务类型选择合适的工作队列
- 在应用关闭时正确关闭线程池
- 避免在线程池中执行死循环任务或阻塞时间过长的任务
- 定期检查和监控线程池状态
注意事项
- 避免使用过大的线程池,过多的线程会导致线程上下文切换开销增加
- 避免使用过小的线程池,可能导致任务队列积压
- 避免在任务中捕获但不处理InterruptedException,这会阻止线程响应中断
- 在循环中提交大量任务时,注意检查队列积压情况
- 关注任务执行时间,避免长时间运行的任务阻塞线程池