在多线程编程中,线程池是Java并发编程的核心组件之一。合理使用线程池可以显著提升系统性能,降低资源消耗。本文将基于JDK8,深入剖析线程池的各个参数含义,并详细讲解四种拒绝策略。
一、线程池核心参数详解
1.1 ThreadPoolExecutor构造函数
public ThreadPoolExecutor( int corePoolSize, // 核心线程数 int maximumPoolSize, // 最大线程数 long keepAliveTime, // 空闲线程存活时间 TimeUnit unit, // 时间单位 BlockingQueue<Runnable> workQueue, // 工作队列 ThreadFactory threadFactory, // 线程工厂 RejectedExecutionHandler handler // 拒绝策略处理器 )1.2 各参数详细说明
1. corePoolSize(核心线程数)
定义:线程池中保持活动状态的最小线程数量
特性:
即使线程处于空闲状态,也不会被回收
除非设置
allowCoreThreadTimeOut(true),此时核心线程空闲超时也会被回收
建议:根据业务负载特点设置,通常设置为CPU密集型任务:CPU核心数+1,IO密集型任务:2*CPU核心数
2. maximumPoolSize(最大线程数)
定义:线程池允许创建的最大线程数量
特性:
当工作队列被占满且核心线程都在忙时,会创建新线程,直到达到此限制
超出核心线程数的线程在空闲时会被回收
3. keepAliveTime(线程空闲时间)
定义:超出核心线程数的线程在空闲时的最大存活时间
与unit配合使用:指定时间单位(秒、毫秒等)
作用:控制线程池中线程的数量,避免资源浪费
4. unit(线程空闲时间单位)
定义:超出核心线程数的线程在空闲时的最大存活时间
与keepAliveTime配合使用:指定时间
作用:控制线程池中线程的数量,避免资源浪费
5. workQueue(工作队列)
用于存放等待执行的任务,常见实现类:
| 队列类型 | 特点 | 适用场景 |
|---|---|---|
| ArrayBlockingQueue | 有界队列,FIFO | 任务量可控,防止资源耗尽 |
| LinkedBlockingQueue | 可选有界/无界,FIFO | 默认无界,任务量大但执行慢 |
| SynchronousQueue | 不存储元素,直接传递 | 高吞吐量,任务处理快 |
| PriorityBlockingQueue | 优先级队列 | 需要按优先级执行任务 |
6. threadFactory(线程工厂)
作用:创建新线程
自定义:可设置线程名称、优先级、是否为守护线程等
默认实现:
Executors.defaultThreadFactory()
7. handler(拒绝策略)
触发条件:当线程池已关闭,或工作队列和线程池都被占满时
策略:有四种内置拒绝策略,下文详细讲解
二、线程池执行流程
// 线程池执行任务的核心流程 1. 提交任务到线程池 2. 如果当前线程数 < corePoolSize,创建新线程执行任务 3. 如果线程数 >= corePoolSize,将任务放入workQueue 4. 如果workQueue已被占满且线程数 < maximumPoolSize,创建新线程执行任务 5. 如果workQueue已被占满且线程数 >= maximumPoolSize,执行拒绝策略三、四种拒绝策略详解
3.1 AbortPolicy(默认策略)
/** * 抛出RejectedExecutionException异常 * 适合需要明确知道任务被拒绝的场景 */ public static class AbortPolicy implements RejectedExecutionHandler { public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { throw new RejectedExecutionException( "Task " + r.toString() + " rejected from " + e.toString()); } }特点:
直接抛出异常,任务不会被执行
调用者可以捕获异常进行相应处理
适合场景:对任务执行有严格要求,需要知道每个任务是否成功提交
3.2 CallerRunsPolicy
/** * 由提交任务的线程直接执行被拒绝的任务 * 相当于让调用者"自己干" */ public static class CallerRunsPolicy implements RejectedExecutionHandler { public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { r.run(); // 直接在调用者线程中运行 } } }特点:
任务不会丢失,但会阻塞提交任务的线程
起到简单的反馈调节作用,降低新任务提交速度
适合场景:不希望丢失任务,且可以接受任务执行速度变慢
3.3 DiscardOldestPolicy
/** * 丢弃队列中最旧的任务,然后重新提交当前任务 */ public static class DiscardOldestPolicy implements RejectedExecutionHandler { public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { e.getQueue().poll(); // 移除队列头部的任务 e.execute(r); // 重新尝试执行当前任务 } } }特点:
丢弃等待时间最长的任务
可能丢失重要任务
适合场景:新任务比旧任务更重要,可以接受丢失部分旧任务
3.4 DiscardPolicy
/** * 静默丢弃被拒绝的任务 * 不做任何处理,就像什么都没发生 */ public static class DiscardPolicy implements RejectedExecutionHandler { public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { // 什么都不做,直接丢弃任务 } }特点:
任务被静默丢弃,无任何提示
可能导致任务丢失而不自知
适合场景:对任务完成率要求不高,允许丢失任务
四、拒绝策略对比与选择
| 策略 | 是否抛出异常 | 是否丢失任务 | 适用场景 |
|---|---|---|---|
| AbortPolicy | ✓ | ✓ | 需要明确知道任务被拒绝,对任务完整性要求高 |
| CallerRunsPolicy | ✗ | ✗ | 不允许任务丢失,可以接受性能下降 |
| DiscardOldestPolicy | ✗ | ✓ | 新任务比旧任务重要,可丢弃等待时间长的任务 |
| DiscardPolicy | ✗ | ✓ | 允许丢失部分任务,如日志记录等非关键任务 |
五、实际应用示例
5.1 自定义线程池配置
public class ThreadPoolExample { public static void main(String[] args) { // 创建自定义线程池 ThreadPoolExecutor executor = new ThreadPoolExecutor( 2, // corePoolSize 5, // maximumPoolSize 60, // keepAliveTime TimeUnit.SECONDS, // unit new ArrayBlockingQueue<>(10), // workQueue new CustomThreadFactory(), // threadFactory new CustomRejectedExecutionHandler() // handler ); // 设置核心线程空闲超时 executor.allowCoreThreadTimeOut(true); // 提交任务 for (int i = 0; i < 20; i++) { final int taskId = i; executor.execute(() -> { System.out.println("执行任务: " + taskId + ", 线程: " + Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }); } // 优雅关闭 executor.shutdown(); } } // 自定义线程工厂 class CustomThreadFactory implements ThreadFactory { private AtomicInteger threadNumber = new AtomicInteger(1); @Override public Thread newThread(Runnable r) { Thread t = new Thread(r, "custom-pool-" + threadNumber.getAndIncrement()); if (t.isDaemon()) { t.setDaemon(false); } if (t.getPriority() != Thread.NORM_PRIORITY) { t.setPriority(Thread.NORM_PRIORITY); } return t; } } // 自定义拒绝策略 class CustomRejectedExecutionHandler implements RejectedExecutionHandler { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { // 记录日志 System.err.println("任务被拒绝: " + r.toString()); // 发送告警 sendAlert(); // 可以根据需要选择其他处理方式 } private void sendAlert() { // 发送告警逻辑 } }六、最佳实践与注意事项
6.1 最佳实践
明确任务类型:区分CPU密集型和IO密集型,配置不同参数
合理设置队列大小:避免OOM,考虑内存限制
监控线程池状态:定期监控活跃线程数、队列大小等指标
优雅关闭:使用
shutdown()或shutdownNow(),配合awaitTermination()
6.2 常见陷阱
无界队列风险:
LinkedBlockingQueue默认无界,可能导致OOM不合理的拒绝策略:错误选择可能导致任务丢失或系统不稳定
忽略线程工厂:线程命名不规范会增加问题排查难度
6.3 监控与调优
// 监控线程池状态 public void monitorThreadPool(ThreadPoolExecutor executor) { System.out.println("核心线程数: " + executor.getCorePoolSize()); System.out.println("当前线程数: " + executor.getPoolSize()); System.out.println("活跃线程数: " + executor.getActiveCount()); System.out.println("最大线程数: " + executor.getMaximumPoolSize()); System.out.println("任务总数: " + executor.getTaskCount()); System.out.println("已完成任务数: " + executor.getCompletedTaskCount()); System.out.println("队列大小: " + executor.getQueue().size()); }