第一部分:线程池基础

理解线程池的核心概念与优势

学习目标

掌握线程池的作用、优势及适用场景

线程创建问题

直接创建线程的缺点:

  • 频繁创建销毁开销大
  • 资源耗尽风险
  • 线程管理困难
  • 无法控制并发数量
// 直接创建线程的示例 for (int i = 0; i < 1000; i++) { new Thread(() -> { // 执行任务 }).start(); // 创建大量线程,可能导致资源耗尽 }

线程池优势

线程池解决的核心问题:

  • 复用线程资源
  • 控制并发数量
  • 统一管理线程
  • 提供任务队列
// 使用线程池的示例 ExecutorService executor = Executors.newFixedThreadPool(10); for (int i = 0; i < 1000; i++) { executor.execute(() -> { // 执行任务 }); } executor.shutdown();
线程1: 执行任务 → 空闲 → 执行新任务
线程2: 执行任务 → 空闲 → 执行新任务
线程3: 执行任务 → 空闲 → 执行新任务

线程池复用线程资源,避免频繁创建销毁

线程池工作原理示意图

线程池核心组件:核心线程、任务队列、非核心线程、拒绝策略

第二部分:线程池创建方式

掌握ExecutorService的多种创建方式

学习目标

理解Executors工厂类与ThreadPoolExecutor构造方法

1. Executors工厂方法

使用Executors提供的工厂方法创建线程池

import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ExecutorsExample { public static void main(String[] args) { // 固定大小线程池 ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5); // 单线程线程池 ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); // 可缓存线程池 ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); // 定时任务线程池 ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(3); } }

2. ThreadPoolExecutor构造方法

直接使用ThreadPoolExecutor创建线程池

import java.util.concurrent.*; public class ThreadPoolExample { public static void main(String[] args) { int corePoolSize = 5; int maxPoolSize = 10; long keepAliveTime = 60; TimeUnit unit = TimeUnit.SECONDS; BlockingQueue workQueue = new ArrayBlockingQueue<>(100); ThreadPoolExecutor executor = new ThreadPoolExecutor( corePoolSize, maxPoolSize, keepAliveTime, unit, workQueue ); } }
创建方法 线程池类型 核心线程数 最大线程数 队列类型 适用场景
newFixedThreadPool 固定大小 n n LinkedBlockingQueue 负载较重服务器
newSingleThreadExecutor 单线程 1 1 LinkedBlockingQueue 任务顺序执行
newCachedThreadPool 可缓存 0 Integer.MAX_VALUE SynchronousQueue 短时异步任务
newScheduledThreadPool 定时任务 n Integer.MAX_VALUE DelayedWorkQueue 定时/周期性任务

选择建议

1. 生产环境推荐使用ThreadPoolExecutor构造方法
2. 避免使用Executors.newFixedThreadPool()和newSingleThreadExecutor()
3. 短时任务可使用newCachedThreadPool()
4. 定时任务使用newScheduledThreadPool()

第三部分:线程池核心参数

深入理解线程池配置参数

学习目标

掌握corePoolSize, maxPoolSize, keepAliveTime, workQueue等参数的作用

corePoolSize(核心线程数)

线程池中保持活动状态的线程数量

特点: 即使空闲也不会被回收

设置建议: 根据CPU核心数和任务类型设置

maxPoolSize(最大线程数)

线程池允许创建的最大线程数量

特点: 当队列满时创建新线程

设置建议: 根据系统资源和任务负载设置

keepAliveTime(空闲时间)

非核心线程空闲时的存活时间

特点: 超时后线程被回收

设置建议: 根据任务到达频率设置

workQueue(任务队列)

保存等待执行的任务队列

常用队列:

  • LinkedBlockingQueue(无界队列)
  • ArrayBlockingQueue(有界队列)
  • SynchronousQueue(直接传递队列)
  • PriorityBlockingQueue(优先级队列)

RejectedExecutionHandler(拒绝策略)

当任务无法执行时的处理策略

内置策略:

  • AbortPolicy(默认):抛出RejectedExecutionException
  • CallerRunsPolicy:由调用线程执行任务
  • DiscardPolicy:直接丢弃任务
  • DiscardOldestPolicy:丢弃队列最前面的任务

ThreadFactory(线程工厂)

创建新线程的工厂类

作用:

  • 自定义线程名称
  • 设置线程优先级
  • 设置线程守护状态
  • 设置UncaughtExceptionHandler

核心线程 (corePoolSize)

长期存活的线程,处理常规任务

任务队列 (workQueue)

保存等待执行的任务

非核心线程 (maxPoolSize - corePoolSize)

临时线程,处理突发任务

拒绝策略 (RejectedExecutionHandler)

当队列和线程都满时处理新任务

第四部分:任务提交与执行

掌握任务提交方式与执行结果获取

学习目标

理解execute()和submit()的区别,掌握Future的使用

execute()方法

提交不需要返回值的任务

ExecutorService executor = Executors.newFixedThreadPool(3); // 提交Runnable任务 executor.execute(() -> { System.out.println("任务执行中: " + Thread.currentThread().getName()); });

特点: 无返回值,无法获取任务执行结果

submit()方法

提交需要返回值的任务

ExecutorService executor = Executors.newFixedThreadPool(3); // 提交Callable任务 Future future = executor.submit(() -> { // 模拟耗时计算 Thread.sleep(1000); return 42; }); // 获取任务结果 try { Integer result = future.get(); System.out.println("计算结果: " + result); } catch (Exception e) { e.printStackTrace(); }

特点: 返回Future对象,可获取任务结果

invokeAll()方法

批量提交任务并等待所有任务完成

ExecutorService executor = Executors.newFixedThreadPool(5); List> tasks = new ArrayList<>(); for (int i = 0; i < 10; i++) { final int taskId = i; tasks.add(() -> { Thread.sleep(1000); return taskId; }); } // 提交所有任务并等待完成 List> futures = executor.invokeAll(tasks); // 获取所有任务结果 for (Future future : futures) { System.out.println("任务结果: " + future.get()); }

invokeAny()方法

提交多个任务,返回任意一个完成的任务结果

ExecutorService executor = Executors.newFixedThreadPool(5); List> tasks = new ArrayList<>(); for (int i = 0; i < 5; i++) { final int taskId = i; tasks.add(() -> { Thread.sleep((long) (Math.random() * 3000)); return taskId; }); } // 返回任意一个完成的任务结果 Integer result = executor.invokeAny(tasks); System.out.println("第一个完成的任务结果: " + result);

第五部分:线程池生命周期

掌握线程池的启动、关闭与状态转换

学习目标

理解线程池状态转换,掌握shutdown()和shutdownNow()的区别

RUNNING

运行状态,接受新任务并处理队列任务

SHUTDOWN

关闭状态,不接受新任务但处理队列任务

STOP

停止状态,不接受新任务,不处理队列任务,中断正在执行的任务

TIDYING

整理状态,所有任务终止,workerCount=0

TERMINATED

终止状态,terminated()方法执行完成

shutdown()方法

平滑关闭线程池

ExecutorService executor = Executors.newFixedThreadPool(3); // 提交任务... executor.execute(() -> { /* 任务1 */ }); executor.execute(() -> { /* 任务2 */ }); // 平滑关闭 executor.shutdown(); // 等待所有任务完成 try { if (!executor.awaitTermination(60, TimeUnit.SECONDS)) { // 超时后强制关闭 executor.shutdownNow(); } } catch (InterruptedException e) { executor.shutdownNow(); Thread.currentThread().interrupt(); }

特点: 等待已提交任务完成,不接受新任务

shutdownNow()方法

立即关闭线程池

ExecutorService executor = Executors.newFixedThreadPool(3); // 提交任务... executor.execute(() -> { /* 任务1 */ }); executor.execute(() -> { /* 任务2 */ }); // 立即关闭 List notExecutedTasks = executor.shutdownNow(); System.out.println("未执行任务数量: " + notExecutedTasks.size());

特点: 尝试中断正在执行的任务,返回未执行任务列表

关闭建议

1. 优先使用shutdown() + awaitTermination()组合
2. 超时后使用shutdownNow()强制关闭
3. 处理中断异常时恢复中断状态
4. 使用isShutdown()和isTerminated()检查状态

第六部分:常见错误与陷阱

避免线程池使用中的常见问题

1. 使用无界队列

导致内存溢出

// 错误示例:使用无界队列 ExecutorService executor = new ThreadPoolExecutor( 5, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>() // 无界队列 ); // 提交大量任务 for (int i = 0; i < 1000000; i++) { executor.execute(() -> { // 任务执行 }); }

正确做法:使用有界队列并设置拒绝策略

2. 忽略拒绝策略

任务被静默丢弃

// 错误示例:未设置拒绝策略 ExecutorService executor = new ThreadPoolExecutor( 5, 5, // 最大线程数等于核心线程数 0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10) // 有界队列 ); // 提交超过队列容量的任务 for (int i = 0; i < 20; i++) { executor.execute(() -> { // 任务执行 }); }

正确做法:设置合适的拒绝策略

3. 忘记关闭线程池

导致线程泄漏,JVM无法退出

// 错误示例:未关闭线程池 public void processTasks() { ExecutorService executor = Executors.newFixedThreadPool(5); executor.execute(() -> { /* 任务 */ }); // 忘记调用executor.shutdown() }

正确做法:使用try-finally或try-with-resources关闭线程池

4. 错误配置线程数

CPU密集型任务配置过多线程

// 错误示例:CPU密集型任务配置过多线程 int cpuCores = Runtime.getRuntime().availableProcessors(); ExecutorService executor = Executors.newFixedThreadPool(cpuCores * 100); // 过多线程

正确做法:CPU密集型任务线程数 ≈ CPU核心数

5. 未处理任务异常

任务异常导致线程终止

// 错误示例:未捕获任务异常 executor.execute(() -> { // 可能抛出异常的操作 processData(); // 未捕获异常 });

正确做法:在任务内部捕获异常或设置UncaughtExceptionHandler

6. 阻塞任务过多

IO密集型任务阻塞线程

// 错误示例:大量阻塞任务占用线程 ExecutorService executor = Executors.newFixedThreadPool(10); for (int i = 0; i < 100; i++) { executor.execute(() -> { // 阻塞操作(如网络请求) httpRequest(); // 长时间阻塞 }); }

正确做法:增加线程数或使用异步非阻塞IO

第七部分:线程池最佳实践

高效、安全地使用线程池

线程池优点

  • 资源复用,减少创建销毁开销
  • 控制并发数量,避免资源耗尽
  • 统一管理线程,提高稳定性
  • 提供任务队列,支持任务调度

线程池缺点

  • 配置复杂,参数调优困难
  • 不当使用可能导致内存溢出
  • 任务异常可能导致线程终止
  • 死锁问题难以排查

实践1:合理配置参数

  • CPU密集型:线程数 ≈ CPU核心数
  • IO密集型:线程数 ≈ CPU核心数 * (1 + 等待时间/计算时间)
  • 混合型:根据任务比例调整
// 合理配置线程池 int cpuCores = Runtime.getRuntime().availableProcessors(); int poolSize = cpuCores * 2; // IO密集型任务 ThreadPoolExecutor executor = new ThreadPoolExecutor( poolSize, // corePoolSize poolSize * 2, // maxPoolSize 60, TimeUnit.SECONDS, // keepAliveTime new ArrayBlockingQueue<>(100), // workQueue new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略 );

实践2:使用有界队列

  • 避免无界队列导致内存溢出
  • 设置合理的队列容量
  • 配合拒绝策略处理队列满的情况
// 使用有界队列 BlockingQueue queue = new ArrayBlockingQueue<>(100); ThreadPoolExecutor executor = new ThreadPoolExecutor( 5, 10, 60, TimeUnit.SECONDS, queue, new ThreadPoolExecutor.AbortPolicy() );

实践3:监控线程池状态

  • 监控线程池状态和队列大小
  • 使用ThreadPoolExecutor的扩展方法
  • 集成监控系统
// 监控线程池状态 ThreadPoolExecutor executor = ...; // 获取状态信息 int activeCount = executor.getActiveCount(); long completedTaskCount = executor.getCompletedTaskCount(); int queueSize = executor.getQueue().size(); int poolSize = executor.getPoolSize(); System.out.printf("活动线程: %d, 已完成任务: %d, 队列大小: %d, 线程池大小: %d%n", activeCount, completedTaskCount, queueSize, poolSize);

高级技巧

1. 使用自定义ThreadFactory设置线程名称
2. 重写beforeExecute和afterExecute添加日志
3. 使用CompletableFuture组合异步任务
4. 使用ForkJoinPool处理分治任务
5. 使用Spring的@Async简化异步调用

第八部分:练习与测验

通过练习巩固线程池知识

动手练习

  • 实现一个自定义拒绝策略
  • 创建监控线程池状态的工具类
  • 使用线程池实现Web服务器请求处理
  • 实现一个可动态调整大小的线程池
1

线程池知识测验

以下关于线程池的描述,哪项是错误的?
以下哪种情况最适合使用newFixedThreadPool?

学习资源推荐

1. 《Java并发编程实战》第6章、第8章
2. Oracle官方文档:java.util.concurrent
3. ThreadPoolExecutor源码分析
4. Java并发编程之美:线程池原理