一、为什么需要 Executors?
在 Java 诞生初期,我们通过new Thread()直接创建线程。这种方式存在严重问题:
// ❌ 反模式:每次请求创建新线程 new Thread(() -> { // 处理任务 }).start();问题:
线程创建销毁开销大(OS 级别操作)
无限制创建导致 OOM 或系统崩溃
缺乏统一管理和监控
Executors 框架(Java 5+)提供了线程池解决方案,实现线程复用、任务队列管理、生命周期控制。
二、核心设计架构
2.1 类层次结构
Interface Executor (顶级接口) └── Interface ExecutorService (扩展生命周期管理) ├── AbstractExecutorService (抽象实现) │ └── ThreadPoolExecutor (核心实现) │ ├── ScheduledThreadPoolExecutor (定时任务) │ └── ForkJoinPool (分治任务) └── ScheduledExecutorService (定时任务接口)2.2 核心接口职责
| 接口 | 核心方法 | 职责 |
|---|---|---|
| Executor | execute(Runnable) | 任务提交与执行解耦 |
| ExecutorService | submit(),shutdown(),awaitTermination() | 生命周期管理 + 返回 Future |
| ScheduledExecutorService | schedule(),scheduleAtFixedRate() | 延迟/周期性任务 |
三、ThreadPoolExecutor 深度解析
3.1 构造函数参数(7 大核心参数)
public ThreadPoolExecutor( int corePoolSize, // 核心线程数 int maximumPoolSize, // 最大线程数 long keepAliveTime, // 空闲线程存活时间 TimeUnit unit, // 时间单位 BlockingQueue<Runnable> workQueue, // 任务队列 ThreadFactory threadFactory, // 线程工厂 RejectedExecutionHandler handler // 拒绝策略 )3.2 工作流程(状态机驱动)
// 线程池运行状态(高3位存储) private static final int RUNNING = -1 << COUNT_BITS; // 111 private static final int SHUTDOWN = 0 << COUNT_BITS; // 000 private static final int STOP = 1 << COUNT_BITS; // 001 private static final int TIDYING = 2 << COUNT_BITS; // 010 private static final int TERMINATED = 3 << COUNT_BITS; // 011任务提交执行流程:
3.3 四种预定义线程池(Executors 工具类)
① FixedThreadPool(固定大小)
ExecutorService pool = Executors.newFixedThreadPool(10); // 核心=最大=10,无超时,LinkedBlockingQueue(无界队列)适用:负载稳定、任务量可预测的场景。
风险:无界队列可能导致 OOM。
② CachedThreadPool(弹性伸缩)
ExecutorService pool = Executors.newCachedThreadPool(); // 核心=0,最大=Integer.MAX_VALUE,超时60s,SynchronousQueue适用:大量短生命周期的任务(如 Web 请求)。
风险:无限创建线程可能耗尽系统资源。
③ SingleThreadExecutor(单线程)
ExecutorService pool = Executors.newSingleThreadExecutor(); // 核心=最大=1,无界队列适用:保证任务顺序执行(如日志写入)。
④ ScheduledThreadPool(定时任务)
ScheduledExecutorService pool = Executors.newScheduledThreadPool(5); pool.scheduleAtFixedRate(() -> {}, 0, 1, TimeUnit.SECONDS);适用:定时任务、心跳检测、延迟重试。
四、拒绝策略(RejectedExecutionHandler)
当队列满且线程数达到最大值时触发:
| 策略 | 行为 | 风险 |
|---|---|---|
| AbortPolicy(默认) | 抛出 RejectedExecutionException | 需捕获处理 |
| CallerRunsPolicy | 由调用者线程执行 | 可能阻塞主流程 |
| DiscardPolicy | 静默丢弃 | 任务丢失不可知 |
| DiscardOldestPolicy | 丢弃队首任务,重新提交 | 旧任务丢失 |
自定义拒绝策略示例:
pool.setRejectedExecutionHandler((r, executor) -> { // 降级处理:记录日志 + 存入数据库待补偿 log.warn("任务被拒绝: {}", r); saveToCompensationQueue(r); });五、实战教程:从新手到进阶
5.1 基础用法
public class ExecutorDemo { public static void main(String[] args) throws Exception { // 1. 创建线程池(推荐手动创建,而非 Executors 工具类) ThreadPoolExecutor executor = new ThreadPoolExecutor( 2, 4, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100), new ThreadFactoryBuilder().setNameFormat("my-pool-%d").build(), new ThreadPoolExecutor.CallerRunsPolicy() ); // 2. 提交任务(三种方式) // 方式一:execute - 无返回值 executor.execute(() -> System.out.println("Runnable task")); // 方式二:submit - 返回 Future Future<String> future = executor.submit(() -> { Thread.sleep(1000); return "Result"; }); System.out.println(future.get()); // 阻塞获取结果 // 方式三:提交批量任务 List<Callable<String>> tasks = Arrays.asList( () -> "Task1", () -> "Task2" ); List<Future<String>> futures = executor.invokeAll(tasks); // 3. 优雅关闭 executor.shutdown(); // 不再接收新任务,等待已有任务完成 if (!executor.awaitTermination(60, TimeUnit.SECONDS)) { executor.shutdownNow(); // 强制中断 } } }5.2 核心监控 API
public void monitor(ThreadPoolExecutor pool) { int activeCount = pool.getActiveCount(); long completedTaskCount = pool.getCompletedTaskCount(); long taskCount = pool.getTaskCount(); int queueSize = pool.getQueue().size(); int poolSize = pool.getPoolSize(); int largestPoolSize = pool.getLargestPoolSize(); System.out.printf("活跃: %d, 完成: %d, 队列: %d, 池大小: %d%n", activeCount, completedTaskCount, queueSize, poolSize); }5.3 自定义 ThreadFactory(为线程命名)
ThreadFactory namedFactory = r -> { Thread t = new Thread(r); t.setName("order-processor-" + ThreadLocalRandom.current().nextInt(1000)); t.setDaemon(false); // 非守护线程 t.setPriority(Thread.NORM_PRIORITY); return t; };5.4 动态调整线程池大小
// 运行时调整(需谨慎) executor.setCorePoolSize(10); executor.setMaximumPoolSize(20);六、高级特性与最佳实践
6.1 使用 Future 处理超时和异常
Future<String> future = executor.submit(() -> { // 可能长时间运行的任务 return "done"; }); try { String result = future.get(3, TimeUnit.SECONDS); } catch (TimeoutException e) { future.cancel(true); // 中断任务 // 降级处理 } catch (ExecutionException e) { Throwable cause = e.getCause(); // 任务内部的异常 }6.2 CompletionService:批量任务取结果
CompletionService<String> cs = new ExecutorCompletionService<>(executor); for (int i = 0; i < 10; i++) { cs.submit(() -> "Task result"); } for (int i = 0; i < 10; i++) { Future<String> future = cs.take(); // 谁先完成先取谁 System.out.println(future.get()); }6.3 线程池嵌套的危险
// ❌ 错误:线程池内再创建线程池(可能导致死锁) executor.submit(() -> { ExecutorService inner = Executors.newSingleThreadExecutor(); Future<?> f = inner.submit(() -> {}); f.get(); // 若 inner 线程池资源不足,外部任务永远阻塞 });6.4 上下文传递(MDC 链路追踪)
// 使用装饰器模式传递日志上下文 public class MdcAwareRunnable implements Runnable { private final Runnable task; private final Map<String, String> contextMap = MDC.getCopyOfContextMap(); public MdcAwareRunnable(Runnable task) { this.task = task; } @Override public void run() { MDC.setContextMap(contextMap); try { task.run(); } finally { MDC.clear(); } } } // 提交时包装 executor.execute(new MdcAwareRunnable(() -> log.info("业务日志")));七、常见陷阱与规避
| 陷阱 | 表现 | 解决方案 |
|---|---|---|
| 队列无限膨胀 | 内存溢出 | 使用有界队列 + 合理拒绝策略 |
| 线程泄漏 | 任务异常未捕获导致线程死掉 | 任务内 try-catch 所有异常 |
| 死锁 | 子任务依赖父任务结果 | 避免提交依赖自身的任务 |
| Shutdown 未调用 | 应用无法退出 | finally 块中调用 shutdown |
| 并行度设置不当 | CPU 利用率低或上下文切换频繁 | 设置 N_CPU = Runtime.availableProcessors() |
八、性能调优建议
8.1 线程数计算公式
CPU 密集型:
N_CPU + 1IO 密集型:
N_CPU * 2(或更多,取决于 IO 等待时间)
int cpuCores = Runtime.getRuntime().availableProcessors(); int poolSize = cpuCores * 2; // IO 密集型8.2 队列选择
| 队列 | 特性 | 适用 |
|---|---|---|
| LinkedBlockingQueue | 无界或有界,FIFO | 任务量可控 |
| ArrayBlockingQueue | 有界,性能高 | 内存严格控制 |
| SynchronousQueue | 不存储,直接交接 | CachedThreadPool |
| PriorityBlockingQueue | 支持优先级 | 任务分轻重缓急 |
8.3 监控指标采集
// 结合 Micrometer 暴露指标 MeterRegistry registry = new SimpleMeterRegistry(); Gauge.builder("executor.queue.size", executor, e -> e.getQueue().size()) .register(registry);九、与 ForkJoinPool 对比
| 特性 | ThreadPoolExecutor | ForkJoinPool |
|---|---|---|
| 设计目的 | 通用任务处理 | 分治任务(任务拆分-合并) |
| 工作窃取 | ❌ | ✅(空闲线程偷取其他队列任务) |
| 任务类型 | Runnable/Callable | ForkJoinTask |
| 典型场景 | Web 请求、异步处理 | 并行计算、大数据处理 |
十、总结速查表
// 推荐的手动创建方式(生产环境) ThreadPoolExecutor pool = new ThreadPoolExecutor( coreSize, // 根据业务评估 maxSize, // 根据资源上限设定 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000), // 必须有界 new NamedThreadFactory("biz-pool"), new ThreadPoolExecutor.CallerRunsPolicy() // 或自定义降级 ); // 生命周期最佳实践 try { pool.execute(task); } finally { pool.shutdown(); if (!pool.awaitTermination(5, TimeUnit.SECONDS)) { pool.shutdownNow(); } }掌握 Executors 框架不仅要会用 API,更要理解其工作流、参数含义和边界条件。生产环境中永远不要用Executors.newFixedThreadPool()等快捷方法,因为它们默认使用无界队列,在流量突增时会导致 OOM。务必使用ThreadPoolExecutor构造函数显式创建,并配合完善的监控和降级策略。
核心设计思想:
"线程复用 + 任务队列 + 状态控制"—— 通过将线程的创建销毁与任务提交解耦,实现资源的高效管理和系统的弹性伸缩。