在处理高并发任务时,如果每个任务都创建一个新线程,会导致系统资源急剧消耗、性能下降。线程池通过复用已创建的线程来执行新任务,大大提高了资源利用效率。本文将深入探讨 Java 线程池的核心原理和实践应用,助你彻底掌握这一多线程开发的重要工具。
一、线程池的基本概念
线程池本质上是一种线程使用模式,它在系统中预先创建一定数量的线程,放入池中统一管理。当有任务需要执行时,从池中取出线程执行,任务执行完后线程不会销毁,而是返回池中等待下一个任务。
1.1 线程池的主要优势
- 降低资源消耗:通过复用线程,减少线程创建和销毁的开销
- 提高响应速度:任务到达时不需要等待线程创建
- 提高线程的可管理性:统一管理、分配、调优
- 提供更多更强大的功能:如定时执行、周期执行等
二、Executor 框架体系结构
Java 中的线程池是通过 Executor 框架实现的,该框架包括:
2.1 核心接口
- Executor: 基础接口,只有一个 execute 方法,接收 Runnable 任务
- ExecutorService: 扩展 Executor,增加了 submit 方法支持 Callable 任务,以及管理线程池的方法
- ThreadPoolExecutor: 标准线程池实现类,大多数场景下使用
- ScheduledExecutorService: 支持定时和周期性任务执行的接口
- ScheduledThreadPoolExecutor: 实现定时和周期性任务的线程池
三、ThreadPoolExecutor 核心参数详解
ThreadPoolExecutor 构造函数包含 7 个参数(最常用的构造方法有 6 个参数):
public ThreadPoolExecutor(
int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 线程空闲时间
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 工作队列
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler // 拒绝策略
)
3.1 参数详细说明
(1) corePoolSize(核心线程数)
- 核心线程数(corePoolSize)定义了线程池中保持活跃的线程数量,即使这些线程处于空闲状态
- 这些线程不会因为空闲而被销毁(除非设置 allowCoreThreadTimeOut 为 true)
(2) maximumPoolSize(最大线程数)
- 最大线程数(maximumPoolSize)定义了线程池能够容纳的最大线程数量
- 当任务队列满了,且活跃线程数小于最大线程数,则会创建新线程
(3) keepAliveTime(线程空闲时间)
- 当线程数大于核心线程数时,多余的空闲线程在终止前等待新任务的最长时间
(4) unit(时间单位)
- keepAliveTime 的时间单位(如秒、毫秒等)
(5) workQueue(工作队列)
- 用于保存等待执行的任务的阻塞队列
- 常用队列类型包括:ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue、PriorityBlockingQueue
(6) threadFactory(线程工厂)
- 用于创建新线程的工厂
- 可以自定义线程名称、优先级、是否为守护线程等
(7) handler(拒绝策略)
- 当线程池和队列都满了,无法接收新任务时的处理策略
3.2 线程池执行流程图
四、四种常见线程池类型及适用场景
下表比较了 Executors 工厂类提供的四种常见线程池类型:
线程池类型 | corePoolSize | maximumPoolSize | keepAliveTime | 工作队列 | 适用场景 |
---|---|---|---|---|---|
FixedThreadPool | 固定值 | 同 corePoolSize | 0 | LinkedBlockingQueue (无界队列) | 固定数量线程的场景, 需注意无界队列 OOM 风险 |
CachedThreadPool | 0 | Integer.MAX_VALUE | 60s | SynchronousQueue | 大量短生命周期任务, 注意线程数上限控制 |
SingleThreadExecutor | 1 | 1 | 0 | LinkedBlockingQueue (无界队列) | 需要保证任务顺序执行, 如日志记录系统 |
ScheduledThreadPool | 固定值 | Integer.MAX_VALUE | 0 | DelayedWorkQueue | 需要执行定时任务或 周期性任务的场景 |
4.1 FixedThreadPool(固定线程数的线程池)
ExecutorService fixedPool = Executors.newFixedThreadPool(5);
- 特点:核心线程数等于最大线程数,不会回收线程,使用无界队列 LinkedBlockingQueue
- 适用场景:适合处理固定数量的长期任务,保持稳定的并发度
- 潜在风险:使用无界队列,当任务持续快速提交而处理速度较慢时,可能导致队列过大,引发内存溢出(OOM)
案例:CPU 密集型计算
// 获取系统CPU核心数作为线程数(CPU密集型任务)
int processors = Runtime.getRuntime().availableProcessors();
// 自定义线程池,使用有界队列防止OOM风险
ThreadPoolExecutor executor = new ThreadPoolExecutor(
processors, // 核心线程数
processors, // 最大线程数
0L, TimeUnit.MILLISECONDS, // 线程不会超时
new ArrayBlockingQueue<>(100), // 有界队列
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy()); // 拒绝策略:调用者执行
for (int i = 0; i < 10; i++) {
final int taskId = i;
executor.execute(() -> {
try {
System.out.println("线程" + Thread.currentThread().getName()
+ "开始执行任务" + taskId);
// 模拟CPU密集型计算
long result = 0;
for (int j = 0; j < 1000000000; j++) {
result += j;
}
System.out.println("任务" + taskId + "计算结果前10位:" +
String.valueOf(result).substring(0, 10));
} catch (Exception e) {
// 捕获所有异常,避免线程终止
System.err.println("任务" + taskId + "执行异常: " + e.getMessage());
}
});
}
executor.shutdown();
4.2 CachedThreadPool(可缓存的线程池)
ExecutorService cachedPool = Executors.newCachedThreadPool();
- 特点:核心线程数为 0,最大线程数为 Integer.MAX_VALUE,线程空闲 60 秒后回收,使用 SynchronousQueue
- 适用场景:适合执行大量短生命周期的异步任务
- 潜在风险:线程数上限接近无限,在任务量突增时可能创建大量线程,压榨系统资源
案例:短时间异步任务处理
// 创建自定义的可缓存线程池,限制最大线程数
ThreadPoolExecutor executor = new ThreadPoolExecutor(
0, 100, // 限制最大线程数为100,避免资源耗尽
60L, TimeUnit.SECONDS,
new SynchronousQueue<>(),
r -> {
Thread t = new Thread(r);
t.setName("async-task-" + t.getId()); // 自定义线程名称,便于问题排查
return t;
});
for (int i = 0; i < 100; i++) {
final int taskId = i;
executor.execute(() -> {
try {
System.out.println("线程" + Thread.currentThread().getName()
+ "开始执行任务" + taskId);
// 模拟短时异步任务
Thread.sleep((long) (Math.random() * 1000));
System.out.println("任务" + taskId + "执行完成");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (Exception e) {
// 捕获异常,避免线程意外终止
System.err.println("任务" + taskId + "执行异常: " + e.getMessage());
}
});
}
executor.shutdown();
4.3 SingleThreadExecutor(单线程的线程池)
ExecutorService singlePool = Executors.newSingleThreadExecutor();
- 特点:核心线程数和最大线程数都为 1,使用无界队列 LinkedBlockingQueue
- 适用场景:适合需要保证任务顺序执行的场景,如日志记录系统
- 潜在风险:使用无界队列,任务堆积可能导致 OOM;单线程执行效率有限
案例:有序任务处理
// 创建单线程执行器,但使用有界队列防止OOM
ThreadPoolExecutor executor = new ThreadPoolExecutor(
1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(500), // 限制队列大小
Executors.defaultThreadFactory(),
(r, e) -> {
// 自定义拒绝策略:记录日志并阻塞提交线程
System.err.println("队列已满,任务被拒绝,当前队列长度:" +
((ThreadPoolExecutor)e).getQueue().size());
try {
// 阻塞提交线程,等待队列有空间
e.getQueue().put(r);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
throw new RejectedExecutionException("任务提交中断", ex);
}
});
List<String> dataList = Arrays.asList("数据1", "数据2", "数据3", "数据4", "数据5");
for (String data : dataList) {
executor.execute(() -> {
try {
System.out.println("线程" + Thread.currentThread().getName()
+ "开始处理数据:" + data);
// 模拟数据处理
Thread.sleep(1000);
System.out.println("数据" + data + "处理完成");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (Exception e) {
System.err.println("处理数据" + data + "时发生异常: " + e.getMessage());
}
});
}
executor.shutdown();
4.4 ScheduledThreadPool(定时线程池)
ScheduledExecutorService scheduledPool =
Executors.newScheduledThreadPool(5);
- 特点:支持定时及周期性任务执行,核心线程数固定,最大线程数为 Integer.MAX_VALUE
- 适用场景:需要执行定时任务或周期性任务的场景
- 潜在风险:使用 DelayedWorkQueue 可能堆积大量待执行任务,导致内存压力
案例:周期性健康检查
// 创建带自定义线程工厂的定时线程池
ScheduledThreadPoolExecutor scheduledPool = new ScheduledThreadPoolExecutor(2,
r -> {
Thread t = new Thread(r);
t.setName("scheduler-" + t.getId());
t.setDaemon(true); // 设为守护线程,防止阻止JVM退出
return t;
});
// 延迟3秒后执行一次
scheduledPool.schedule(() -> {
try {
System.out.println("系统启动检查,线程:" + Thread.currentThread().getName()
+ ",时间:" + new Date());
} catch (Exception e) {
System.err.println("启动检查异常: " + e.getMessage());
}
}, 3, TimeUnit.SECONDS);
// 延迟1秒后,每2秒执行一次
scheduledPool.scheduleAtFixedRate(() -> {
try {
System.out.println("定时健康检查,线程:" + Thread.currentThread().getName()
+ ",时间:" + new Date());
// 模拟检查过程
Thread.sleep(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (Exception e) {
// 捕获异常,防止周期性任务中断
System.err.println("健康检查异常: " + e.getMessage());
}
}, 1, 2, TimeUnit.SECONDS);
// 15秒后关闭线程池
try {
Thread.sleep(15000);
// 优雅关闭,等待现有任务完成
scheduledPool.shutdown();
if (!scheduledPool.awaitTermination(5, TimeUnit.SECONDS)) {
// 强制关闭
scheduledPool.shutdownNow();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
scheduledPool.shutdownNow();
}
五、工作队列类型及选择策略
5.1 常用工作队列类型
队列类型 | 特性 | 适用场景 | 示例配置 |
---|---|---|---|
ArrayBlockingQueue | 有界数组队列,FIFO | 明确任务量的有界场景 | new ArrayBlockingQueue<>(100) |
LinkedBlockingQueue | 可指定容量的链表队列 | 需设置容量避免 OOM | new LinkedBlockingQueue<>(1000) |
SynchronousQueue | 无容量,直接交付任务 | 需快速响应的场景 | new SynchronousQueue<>() |
PriorityBlockingQueue | 基于优先级的无界队列 | 任务有优先级的场景 | new PriorityBlockingQueue<>() |
DelayQueue | 延迟获取元素的无界队列 | 延迟任务执行场景 | new DelayQueue() |
(1) ArrayBlockingQueue(有界队列)
- 基于数组的有界阻塞队列,必须指定队列大小
- 适用场景:明确知道任务量的有界场景,可以防止资源耗尽
// 队列大小计算示例:
// 假设峰值QPS=1000,任务处理平均耗时=200ms
// 冗余系数2.0用于应对流量突发和任务处理时间波动,确保系统稳定性
int queueSize = (int)(1000 * (200 / 1000.0) * 2.0); // 峰值QPS × 平均处理耗时 × 冗余系数
BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(queueSize);
(2) LinkedBlockingQueue(可设置容量的队列)
- 基于链表的阻塞队列,可以指定容量,不指定则为无界队列
- 适用场景:需要高吞吐量但需要控制内存占用的场景
// 有界队列,避免OOM风险
BlockingQueue<Runnable> boundedQueue = new LinkedBlockingQueue<>(1000);
// 无界队列(谨慎使用)
// BlockingQueue<Runnable> unboundedQueue = new LinkedBlockingQueue<>();
(3) SynchronousQueue(同步队列)
- 没有容量的队列,每个插入操作都必须等待一个相应的移除操作
- 适用场景:要求将任务直接提交给线程而不是存储在队列中的场景
BlockingQueue<Runnable> syncQueue = new SynchronousQueue<>();
// 使用SynchronousQueue时,通常需要配置较大的maximumPoolSize
// 或合适的拒绝策略,因为队列不存储任务
(4) PriorityBlockingQueue(优先级队列)
- 支持优先级的无界阻塞队列
- 适用场景:任务具有优先级的场景,如任务调度系统
// 自定义优先级任务
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() {
try {
System.out.println("执行任务:" + name + ",优先级:" + priority);
} catch (Exception e) {
System.err.println("任务执行异常:" + e.getMessage());
}
}
@Override
public int compareTo(PriorityTask other) {
// 数字越小优先级越高
return Integer.compare(this.priority, other.priority);
}
}
// 使用优先级队列
BlockingQueue<Runnable> priorityQueue = new PriorityBlockingQueue<>();
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, 5, 60, TimeUnit.SECONDS, priorityQueue);
executor.execute(new PriorityTask(10, "低优先级任务"));
executor.execute(new PriorityTask(1, "高优先级任务"));
executor.execute(new PriorityTask(5, "中优先级任务"));
5.2 队列选择策略
六、拒绝策略详解
当线程池的任务缓存队列已满且线程池中的线程数达到最大线程数时,如果还有任务到来,必须采取一种策略来处理这些任务。
6.1 JDK 提供的四种拒绝策略对比
拒绝策略 | 行为 | 适用场景 | 优缺点 |
---|---|---|---|
AbortPolicy | 抛出 RejectedExecutionException | 任务必须执行成功的场景 | 简单明了,但需要调用方处理异常 |
DiscardPolicy | 静默丢弃任务 | 任务可丢弃的场景 | 不会影响主流程,但可能丢失重要任务 |
DiscardOldestPolicy | 丢弃队列头部任务,执行新任务 | 新任务优先级高的场景 | 保证新任务执行,但可能丢弃重要任务 |
CallerRunsPolicy | 调用者线程执行任务 | 需要反馈压力的场景 | 不会丢失任务,但可能阻塞调用者 |
AbortPolicy(默认策略)
- 直接抛出 RejectedExecutionException 异常,阻止系统继续运行
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, 2,
0L, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(2),
new ThreadPoolExecutor.AbortPolicy());
// 模拟提交超出容量的任务
try {
for (int i = 0; i < 6; i++) {
final int taskId = i;
executor.execute(() -> {
try {
Thread.sleep(1000);
System.out.println("任务" + taskId + "执行完毕");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (Exception e) {
System.err.println("任务" + taskId + "执行异常: " + e.getMessage());
}
});
System.out.println("成功提交任务" + i);
}
} catch (RejectedExecutionException e) {
System.out.println("任务被拒绝: " + e.getMessage());
// 可以在这里添加任务重试逻辑或告警
}
DiscardPolicy(静默丢弃)
- 丢弃任务,但不抛出异常,基本上静默丢弃,对程序无影响
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, 2,
0L, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(2),
new ThreadPoolExecutor.DiscardPolicy());
// 静默丢弃多余的任务
for (int i = 0; i < 6; i++) {
final int taskId = i;
executor.execute(() -> {
try {
Thread.sleep(1000);
System.out.println("任务" + taskId + "执行完毕");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (Exception e) {
System.err.println("任务" + taskId + "执行异常: " + e.getMessage());
}
});
System.out.println("尝试提交任务" + i);
}
DiscardOldestPolicy(丢弃最老任务)
- 丢弃队列最前面的任务,然后重新提交被拒绝的任务
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, 2,
0L, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(2),
new ThreadPoolExecutor.DiscardOldestPolicy());
// 丢弃最老的任务
for (int i = 0; i < 6; i++) {
final int taskId = i;
executor.execute(() -> {
try {
Thread.sleep(1000);
System.out.println("任务" + taskId + "执行完毕");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (Exception e) {
System.err.println("任务" + taskId + "执行异常: " + e.getMessage());
}
});
System.out.println("尝试提交任务" + i);
}
CallerRunsPolicy(调用者运行)
- 由调用者所在的线程来执行任务,这种策略会降低新任务的提交速度,对系统起到自我调节作用
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, 2,
0L, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(2),
new ThreadPoolExecutor.CallerRunsPolicy());
// 调用者线程执行被拒绝的任务
for (int i = 0; i < 10; i++) {
final int taskId = i;
System.out.println("准备提交任务" + i);
executor.execute(() -> {
try {
System.out.println("线程" + Thread.currentThread().getName()
+ "开始执行任务" + taskId);
Thread.sleep(1000);
System.out.println("任务" + taskId + "执行完毕");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (Exception e) {
System.err.println("任务" + taskId + "执行异常: " + e.getMessage());
}
});
}
6.2 自定义拒绝策略
你也可以实现 RejectedExecutionHandler 接口来自定义拒绝策略:
public class LogAndRetryPolicy implements RejectedExecutionHandler {
private final int maxRetries;
private final long retryInterval;
private final Logger logger = Logger.getLogger(LogAndRetryPolicy.class.getName());
public LogAndRetryPolicy(int maxRetries, long retryInterval) {
this.maxRetries = maxRetries;
this.retryInterval = retryInterval;
}
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
if (executor.isShutdown()) {
// 线程池已关闭,放弃任务但记录信息
logger.warning("线程池已关闭,任务被拒绝");
// 可以选择将任务存储到持久化系统或告警系统
saveRejectedTask(r);
return;
}
for (int i = 0; i < maxRetries; i++) {
logger.info("任务被拒绝,正在进行第" + (i+1) + "次重试...");
try {
Thread.sleep(retryInterval);
// 尝试再次提交
if (!executor.isShutdown()) {
// 查看队列是否有空间
if (executor.getQueue().offer(r)) {
logger.info("重试成功,任务已加入队列");
return;
}
// 队列仍满,但可能有线程完成了任务
executor.execute(r);
logger.info("重试成功,任务已提交");
return;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
logger.warning("重试过程被中断");
break;
} catch (RejectedExecutionException e) {
// 继续重试
logger.warning("第" + (i+1) + "次重试失败");
}
}
// 记录最终拒绝信息,写入到持久化存储或告警系统
logger.severe("达到最大重试次数" + maxRetries + ",任务最终被丢弃");
saveRejectedTask(r);
}
// 将被拒绝的任务保存到持久化存储
private void saveRejectedTask(Runnable r) {
try {
// 实际实现可能是写入数据库、消息队列或日志系统
logger.info("任务已保存到持久化存储,稍后可重新提交");
} catch (Exception e) {
logger.severe("保存被拒绝任务失败: " + e.getMessage());
}
}
}
// 使用自定义拒绝策略
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, 2,
0L, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(2),
new LogAndRetryPolicy(3, 1000));
七、线程池生命周期管理
线程池有五种状态,它们定义了线程池的整个生命周期:
- RUNNING: 接受新任务并处理队列任务
- SHUTDOWN: 不接受新任务,但处理队列任务
- STOP: 不接受新任务,不处理队列任务,并中断正在进行的任务
- TIDYING: 所有任务已终止,线程池会调用 terminated()钩子方法
- TERMINATED: terminated()执行完成
7.1 正确关闭线程池
ExecutorService executor = Executors.newFixedThreadPool(5);
// 提交任务...
// 方法1: 温和关闭
executor.shutdown(); // 不再接受新任务,等待已提交任务执行完成
// 等待终止(带超时)
try {
// 设置合理的超时时间,避免永久等待
if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
// 超时后,尝试强制关闭
List<Runnable> droppedTasks = executor.shutdownNow();
System.out.println("线程池未能在10秒内完全关闭,丢弃了" + droppedTasks.size() + "个任务");
// 再次等待,确保关闭
if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
System.err.println("线程池仍未终止,可能存在无法中断的任务");
}
}
} catch (InterruptedException e) {
// 当前线程被中断
executor.shutdownNow();
Thread.currentThread().interrupt();
}
7.2 监控线程池状态
代码方式监控
ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(5);
// 定时监控线程池状态
ScheduledExecutorService monitorService = Executors.newSingleThreadScheduledExecutor();
monitorService.scheduleAtFixedRate(() -> {
System.out.println("=== 线程池状态监控 ===");
System.out.println("线程池大小: " + executor.getPoolSize());
System.out.println("活跃线程数: " + executor.getActiveCount());
System.out.println("已完成任务数: " + executor.getCompletedTaskCount());
System.out.println("队列任务数: " + executor.getQueue().size());
System.out.println("==================");
}, 0, 5, TimeUnit.SECONDS);
// 使用完毕后关闭监控
// monitorService.shutdown();
使用 JMX 监控
import java.lang.management.ManagementFactory;
import javax.management.*;
public class ThreadPoolMonitor {
public static void monitor(ThreadPoolExecutor executor, String poolName) throws Exception {
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
// 创建MBean对象
ObjectName name = new ObjectName("ThreadPools:type=ThreadPoolExecutor,name=" + poolName);
// 注册MBean
ThreadPoolExecutorMBean mbean = new ThreadPoolExecutorMBean(executor);
mbs.registerMBean(mbean, name);
System.out.println("JMX监控已启用,可通过JConsole查看线程池" + poolName + "的状态");
}
// 定义MBean接口
public interface ThreadPoolExecutorMXBean {
int getPoolSize();
int getActiveCount();
long getCompletedTaskCount();
int getQueueSize();
int getCorePoolSize();
int getMaximumPoolSize();
}
// 实现MBean
static class ThreadPoolExecutorMBean implements ThreadPoolExecutorMXBean {
private final ThreadPoolExecutor executor;
public ThreadPoolExecutorMBean(ThreadPoolExecutor executor) {
this.executor = executor;
}
@Override
public int getPoolSize() {
return executor.getPoolSize();
}
@Override
public int getActiveCount() {
return executor.getActiveCount();
}
@Override
public long getCompletedTaskCount() {
return executor.getCompletedTaskCount();
}
@Override
public int getQueueSize() {
return executor.getQueue().size();
}
@Override
public int getCorePoolSize() {
return executor.getCorePoolSize();
}
@Override
public int getMaximumPoolSize() {
return executor.getMaximumPoolSize();
}
}
}
// 使用示例
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100));
ThreadPoolMonitor.monitor(executor, "BusinessPool");
八、实战案例分析:在线商城订单处理系统
假设我们需要设计一个在线商城的订单处理系统,涉及多种任务类型:
- 订单验证(快速任务)
- 库存检查(IO 密集型任务)
- 支付处理(需要外部 API 调用)
- 发送确认邮件(低优先级任务)
8.1 系统设计
8.2 线程池配置方案
为不同类型的任务配置不同的线程池:
import java.util.concurrent.*;
import java.util.logging.Logger;
public class OrderProcessingSystem {
private static final Logger logger = Logger.getLogger(OrderProcessingSystem.class.getName());
// 快速任务的线程池
private final ExecutorService fastTaskPool;
// IO密集型任务的线程池
private final ExecutorService ioTaskPool;
// 低优先级任务的线程池
private final ExecutorService lowPriorityTaskPool;
// 定时任务的线程池
private final ScheduledExecutorService scheduledPool;
public OrderProcessingSystem() {
int cpuCores = Runtime.getRuntime().availableProcessors();
// 计算线程池参数
// 快速验证任务:每个任务平均执行时间20ms,峰值QPS=1000
int fastPoolSize = cpuCores + 1; // CPU密集型任务
int fastQueueSize = (int)(1000 * 0.02 * 1.5); // 峰值QPS × 平均处理时间 × 冗余系数
// IO任务:平均执行时间200ms,IO阻塞系数约为0.8
int ioPoolSize = (int)(cpuCores / (1 - 0.8)); // IO密集型任务
int ioQueueSize = (int)(500 * 0.2 * 2); // 假设峰值QPS=500,冗余系数2.0用于应对突发流量
// 处理快速任务,核心线程数较少,但可快速扩展
fastTaskPool = new ThreadPoolExecutor(
fastPoolSize, fastPoolSize * 2, 60L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(fastQueueSize),
new ThreadFactoryBuilder().setNameFormat("fast-task-%d").build(),
new ThreadPoolExecutor.CallerRunsPolicy());
// 处理IO密集型任务,核心线程数较多
ioTaskPool = new ThreadPoolExecutor(
ioPoolSize, ioPoolSize * 2, 60L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(ioQueueSize),
new ThreadFactoryBuilder().setNameFormat("io-task-%d").build(),
new ThreadPoolExecutor.CallerRunsPolicy());
// 处理低优先级任务,使用有界队列
lowPriorityTaskPool = new ThreadPoolExecutor(
5, 10, 60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000), // 使用有界队列避免OOM
new ThreadFactoryBuilder().setNameFormat("low-priority-%d").build(),
new ThreadPoolExecutor.DiscardPolicy()); // 低优先级任务可丢弃
// 处理定时任务
scheduledPool = Executors.newScheduledThreadPool(3,
new ThreadFactoryBuilder().setNameFormat("scheduler-%d").build());
// 启动监控
startMonitoring();
}
// 监控线程池状态
private void startMonitoring() {
scheduledPool.scheduleAtFixedRate(() -> {
try {
logger.info("===== 线程池状态监控 =====");
logger.info("快速任务线程池 - 活跃线程: " +
((ThreadPoolExecutor)fastTaskPool).getActiveCount() +
", 队列大小: " + ((ThreadPoolExecutor)fastTaskPool).getQueue().size());
logger.info("IO任务线程池 - 活跃线程: " +
((ThreadPoolExecutor)ioTaskPool).getActiveCount() +
", 队列大小: " + ((ThreadPoolExecutor)ioTaskPool).getQueue().size());
logger.info("低优先级任务线程池 - 活跃线程: " +
((ThreadPoolExecutor)lowPriorityTaskPool).getActiveCount() +
", 队列大小: " + ((ThreadPoolExecutor)lowPriorityTaskPool).getQueue().size());
} catch (Exception e) {
logger.severe("监控线程池状态出错: " + e.getMessage());
}
}, 1, 5, TimeUnit.SECONDS);
}
// 订单验证(快速任务)
public void validateOrder(Order order) {
fastTaskPool.execute(() -> {
try {
logger.info("验证订单: " + order.getId());
// 验证逻辑...
checkInventory(order);
} catch (Exception e) {
logger.severe("订单验证异常: " + e.getMessage());
}
});
}
// 库存检查(IO密集型任务)
private void checkInventory(Order order) {
ioTaskPool.execute(() -> {
try {
logger.info("检查库存: " + order.getId());
// 模拟数据库操作
Thread.sleep(200);
boolean inventoryAvailable = Math.random() > 0.1; // 90%概率库存充足
if (inventoryAvailable) {
processPayment(order);
} else {
handleOrderFailure(order, "库存不足");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (Exception e) {
logger.severe("库存检查异常: " + e.getMessage());
handleOrderFailure(order, "库存检查异常");
}
});
}
// 支付处理(IO密集型任务)
private void processPayment(Order order) {
ioTaskPool.execute(() -> {
try {
logger.info("处理支付: " + order.getId());
int retryCount = 0;
boolean paymentSuccessful = false;
// 支付处理重试逻辑
while (retryCount < 3 && !paymentSuccessful) {
try {
// 模拟外部API调用
Thread.sleep(500);
paymentSuccessful = Math.random() > 0.2; // 80%概率支付成功
if (!paymentSuccessful) {
retryCount++;
logger.warning("订单" + order.getId() + "支付失败,正在进行第" + retryCount + "次重试...");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
} catch (Exception e) {
logger.severe("支付处理重试异常: " + e.getMessage());
break;
}
}
if (paymentSuccessful) {
sendConfirmationEmail(order);
} else {
handleOrderFailure(order, "支付失败,已重试" + retryCount + "次");
}
} catch (Exception e) {
logger.severe("支付处理异常: " + e.getMessage());
handleOrderFailure(order, "支付处理异常");
}
});
}
// 发送确认邮件(低优先级任务)
private void sendConfirmationEmail(Order order) {
lowPriorityTaskPool.execute(() -> {
try {
logger.info("发送确认邮件: " + order.getId());
// 模拟邮件发送
Thread.sleep(100);
// 异步通知订单完成
CompletableFuture.runAsync(() -> {
try {
logger.info("订单" + order.getId() + "处理完成");
} catch (Exception e) {
logger.warning("订单完成通知异常: " + e.getMessage());
}
});
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (Exception e) {
logger.warning("邮件发送异常: " + e.getMessage());
// 邮件发送失败不影响订单流程
}
});
}
// 处理订单失败
private void handleOrderFailure(Order order, String reason) {
fastTaskPool.execute(() -> {
try {
logger.warning("订单失败: " + order.getId() + ", 原因: " + reason);
// 失败处理逻辑...
// 可以发送失败通知、记录日志等
} catch (Exception e) {
logger.severe("处理订单失败异常: " + e.getMessage());
}
});
}
// 定时清理过期订单(每小时执行一次)
public void scheduleOrderCleanup() {
scheduledPool.scheduleAtFixedRate(() -> {
try {
logger.info("执行过期订单清理...");
// 清理逻辑...
} catch (Exception e) {
logger.severe("订单清理异常: " + e.getMessage());
}
}, 1, 60, TimeUnit.MINUTES);
}
// 关闭所有线程池
public void shutdown() {
logger.info("正在关闭订单处理系统...");
// 依次关闭各个线程池
fastTaskPool.shutdown();
ioTaskPool.shutdown();
lowPriorityTaskPool.shutdown();
scheduledPool.shutdown();
try {
// 等待所有任务完成
if (!fastTaskPool.awaitTermination(5, TimeUnit.SECONDS))
fastTaskPool.shutdownNow();
if (!ioTaskPool.awaitTermination(10, TimeUnit.SECONDS))
ioTaskPool.shutdownNow();
if (!lowPriorityTaskPool.awaitTermination(3, TimeUnit.SECONDS))
lowPriorityTaskPool.shutdownNow();
if (!scheduledPool.awaitTermination(3, TimeUnit.SECONDS))
scheduledPool.shutdownNow();
logger.info("所有线程池已关闭");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
logger.severe("关闭过程被中断");
}
}
// 内部类:订单
static class Order {
private final String id;
private final double amount;
public Order(String id, double amount) {
this.id = id;
this.amount = amount;
}
public String getId() {
return id;
}
public double getAmount() {
return amount;
}
}
// 内部类:线程工厂构建器
static class ThreadFactoryBuilder {
private String nameFormat;
public ThreadFactoryBuilder setNameFormat(String nameFormat) {
this.nameFormat = nameFormat;
return this;
}
public ThreadFactory build() {
return r -> {
Thread thread = new Thread(r);
if (nameFormat != null) {
thread.setName(String.format(nameFormat, thread.getId()));
}
// 设置未捕获异常处理器,防止线程因异常而终止
thread.setUncaughtExceptionHandler((t, e) -> {
Logger.getLogger(t.getName()).severe("线程" + t.getName() + "发生未捕获异常: " + e.getMessage());
});
return thread;
};
}
}
// 测试方法
public static void main(String[] args) {
OrderProcessingSystem system = new OrderProcessingSystem();
system.scheduleOrderCleanup();
// 模拟处理100个订单
for (int i = 1; i <= 100; i++) {
final String orderId = "ORD-" + i;
final double amount = 100 + Math.random() * 900; // 随机订单金额
system.validateOrder(new Order(orderId, amount));
// 适当暂停,模拟订单到达间隔
try {
Thread.sleep(50);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
// 等待一段时间后关闭系统
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
system.shutdown();
}
}
8.3 使用 CompletableFuture 优化异步流程
public void processOrderAsync(Order order) {
// 使用CompletableFuture构建异步处理链
CompletableFuture.supplyAsync(() -> {
// 订单验证
logger.info("验证订单: " + order.getId());
// 验证逻辑...
return order;
}, fastTaskPool)
.thenApplyAsync(validOrder -> {
// 库存检查
logger.info("检查库存: " + validOrder.getId());
try {
Thread.sleep(200); // 模拟数据库操作
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new CompletionException(e);
}
boolean inventoryAvailable = Math.random() > 0.1; // 90%概率库存充足
if (!inventoryAvailable) {
throw new CompletionException(
new RuntimeException("库存不足: " + validOrder.getId()));
}
return validOrder;
}, ioTaskPool)
.thenApplyAsync(inStockOrder -> {
// 支付处理
logger.info("处理支付: " + inStockOrder.getId());
try {
Thread.sleep(500); // 模拟外部API调用
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new CompletionException(e);
}
boolean paymentSuccessful = Math.random() > 0.2; // 80%概率支付成功
if (!paymentSuccessful) {
throw new CompletionException(
new RuntimeException("支付失败: " + inStockOrder.getId()));
}
return inStockOrder;
}, ioTaskPool)
.thenAcceptAsync(paidOrder -> {
// 发送确认邮件
logger.info("发送确认邮件: " + paidOrder.getId());
try {
Thread.sleep(100); // 模拟邮件发送
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
logger.info("订单" + paidOrder.getId() + "处理完成");
}, lowPriorityTaskPool)
.exceptionally(ex -> {
// 统一异常处理
logger.severe("订单处理异常: " + ex.getMessage());
handleOrderFailure(order, ex.getMessage());
return null;
});
}
九、常见问题及解决方案
9.1 线程池参数如何调优?
线程池参数调优是一个复杂的过程,需要根据具体的业务场景来决定:
核心线程数设置:
- 对于 CPU 密集型任务:核心线程数 = CPU 核心数 + 1
对于 IO 密集型任务:核心线程数 = CPU 核心数 / (1 - 阻塞系数)
- 阻塞系数:任务在阻塞状态的时间占比,通常在 0.8~0.9 之间
- 例如:8 核 CPU,任务有 80%时间在等待 IO,则核心线程数 = 8/(1-0.8) = 40
- 线程数与系统吞吐量关系:
增加线程数并不总是提高系统吞吐量,实际上遵循下图所示的曲线:
队列类型和大小:
- 队列大小计算公式:队列容量 = 峰值 QPS × 平均任务处理时间 × 冗余系数
冗余系数(通常为 1.5-3.0)的作用:
- 应对突发流量(流量可能瞬间超过平均峰值)
- 缓冲任务处理时间波动(某些任务可能比平均时间长)
- 避免频繁触发拒绝策略(减少拒绝策略的执行频率)
实际调优案例:
// 数据库连接池场景 // 假设:CPU是8核,任务90%时间在等待数据库响应 int corePoolSize = (int)(8 / (1 - 0.9)); // 约80线程 // 网络爬虫场景 // 假设:CPU是4核,任务95%时间在等待网络响应 int crawlerThreads = (int)(4 / (1 - 0.95)); // 约80线程 // 计算密集型任务 // 假设:CPU是16核,任务基本无IO int computeThreads = 16 + 1; // 17线程
9.2 如何避免内存溢出风险?
使用线程池时,需要注意以下几点避免内存溢出:
线程数量控制:
// 避免的策略:无限制创建线程 ExecutorService badExecutor = Executors.newCachedThreadPool(); // 推荐策略:限制最大线程数 ExecutorService goodExecutor = new ThreadPoolExecutor( 10, 100, 60L, TimeUnit.SECONDS, new SynchronousQueue<>());
队列大小限制:
// 避免的策略:使用无界队列 ExecutorService riskyExecutor = new ThreadPoolExecutor( 10, 10, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>()); // 无界队列 // 推荐策略:使用有界队列 ExecutorService safeExecutor = new ThreadPoolExecutor( 10, 20, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2000), // 有界队列 new ThreadPoolExecutor.CallerRunsPolicy()); // 合理的拒绝策略
任务大小控制:
// 批量处理大任务时分片 void processLargeDataSet(List<Data> dataSet) { int batchSize = 1000; int totalSize = dataSet.size(); for (int i = 0; i < totalSize; i += batchSize) { int end = Math.min(i + batchSize, totalSize); List<Data> batch = dataSet.subList(i, end); executor.submit(() -> processBatch(batch)); } }
资源释放保证:
executor.execute(() -> { Resource resource = null; try { resource = acquireResource(); // 使用资源处理任务 } catch (Exception e) { // 记录异常日志 logger.severe("任务执行异常: " + e.getMessage()); } finally { // 确保资源释放 if (resource != null) { try { resource.close(); } catch (Exception e) { logger.warning("关闭资源异常: " + e.getMessage()); } } } });
9.3 任务超时处理的多种方式
// 方法1:使用Future的get方法设置超时
ExecutorService executor = Executors.newFixedThreadPool(5);
Future<?> future = executor.submit(() -> {
// 长时间运行的任务
});
try {
// 等待任务完成,但最多等待5秒
future.get(5, TimeUnit.SECONDS);
} catch (TimeoutException e) {
// 任务超时
future.cancel(true); // 尝试取消任务
System.out.println("任务执行超时");
} catch (Exception e) {
// 其他异常处理
}
// 方法2:使用CompletableFuture的超时方法(Java 9+特性)
// 如果使用Java 8,可以使用下面的替代方案
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
// 长时间运行的任务
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return "任务结果";
}, executor);
// Java 9+版本:
if (false) { // 仅为演示,实际代码中应根据Java版本判断
future = future.orTimeout(1, TimeUnit.SECONDS)
.exceptionally(ex -> {
if (ex instanceof TimeoutException) {
return "任务超时";
}
return "任务异常: " + ex.getMessage();
});
}
// Java 8兼容版本:
CompletableFuture<String> timeout = new CompletableFuture<>();
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.schedule(() -> {
timeout.complete("超时");
}, 1, TimeUnit.SECONDS);
// 使用两个CompletableFuture竞争
String result = CompletableFuture.anyOf(future, timeout)
.thenApply(obj -> {
if ("超时".equals(obj)) {
future.cancel(true);
return "任务超时";
}
return (String) obj;
})
.get(); // 这里可以安全地使用get(),因为肯定有一个会完成
// 不要忘记关闭调度器
scheduler.shutdown();
// 方法3:自定义一个带超时控制的任务包装器
public static <T> T runWithTimeout(Callable<T> callable, long timeout, TimeUnit unit)
throws Exception {
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<T> future = executor.submit(callable);
try {
return future.get(timeout, unit);
} catch (Exception e) {
future.cancel(true);
throw e;
} finally {
executor.shutdownNow();
}
}
// 使用示例
try {
String result = runWithTimeout(
() -> {
// 任务逻辑
Thread.sleep(1000);
return "完成";
},
500, TimeUnit.MILLISECONDS
);
} catch (TimeoutException e) {
System.out.println("任务执行超时");
}
9.4 优雅处理 ThreadLocal 内存泄漏
// 定义带清理功能的线程工厂
ThreadFactory threadFactory = new ThreadFactory() {
private final AtomicInteger threadNumber = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
// 包装原始任务,确保ThreadLocal变量清理
Runnable taskWrapper = () -> {
try {
r.run();
} finally {
cleanupThreadLocals();
}
};
Thread t = new Thread(taskWrapper, "pool-thread-" + threadNumber.getAndIncrement());
// 设置未捕获异常处理器
t.setUncaughtExceptionHandler((thread, ex) -> {
System.err.println("线程" + thread.getName() + "发生未捕获异常: " + ex.getMessage());
// 确保清理ThreadLocal,即使发生异常
cleanupThreadLocals();
});
return t;
}
// 清理当前线程的ThreadLocal变量
private void cleanupThreadLocals() {
try {
// 清理已知的ThreadLocal变量
threadLocalUserContext.remove();
threadLocalTransaction.remove();
// 其他ThreadLocal变量...
// 也可以记录日志,方便调试ThreadLocal泄漏问题
System.out.println("已清理线程" + Thread.currentThread().getName() + "的ThreadLocal变量");
} catch (Exception e) {
System.err.println("清理ThreadLocal变量异常: " + e.getMessage());
}
}
};
// 使用自定义线程工厂
ThreadPoolExecutor executor = new ThreadPoolExecutor(
10, 20, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100),
threadFactory);
十、总结
下表总结了 Java 线程池的核心知识点:
知识点 | 关键内容 | 应用建议 |
---|---|---|
线程池核心参数 | corePoolSize、maximumPoolSize、keepAliveTime、workQueue、rejectionPolicy | CPU 密集型任务核心线程数约等于 CPU 核心数+1 IO 密集型任务核心线程数=CPU 核心数/(1-阻塞系数) |
任务提交流程 | 核心线程 → 工作队列 → 非核心线程 → 拒绝策略 | 理解任务提交流程有助于合理配置参数 避免资源耗尽 |
线程池类型选择 | FixedThreadPool、CachedThreadPool、SingleThreadPool、ScheduledThreadPool | 避免直接使用 Executors 工厂方法 根据业务场景自定义 ThreadPoolExecutor |
队列类型选择 | ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue、PriorityBlockingQueue | 使用有界队列避免 OOM 风险 队列大小=峰值 QPS× 平均处理时间 × 冗余系数 |
拒绝策略应用 | AbortPolicy、DiscardPolicy、DiscardOldestPolicy、CallerRunsPolicy | 根据业务容错需求选择合适的拒绝策略 考虑自定义拒绝策略实现重试机制 |
线程池监控 | 线程数监控、队列大小监控、任务完成情况监控 | 结合 JMX 实现可视化监控 设置合理的告警阈值 |
异常处理 | 任务执行异常、线程中断处理 | 使用 try-catch 包装任务逻辑 设置 UncaughtExceptionHandler |
资源释放 | 线程池关闭、ThreadLocal 清理 | 使用 shutdown()优雅关闭 必要时使用 shutdownNow()强制关闭 |
线程池是 Java 并发编程中非常重要的工具,掌握其核心原理和使用方法对于构建高性能、稳定的并发应用至关重要。通过合理配置线程池参数,选择恰当的队列类型和拒绝策略,可以显著提升应用的并发处理能力和资源利用效率。
实际应用中,需要根据具体业务场景进行线程池调优,并建立有效的监控机制,确保系统在高负载情况下依然能够稳定运行。
感谢您耐心阅读到这里!如果觉得本文对您有帮助,欢迎点赞 👍、收藏 ⭐、分享给需要的朋友,您的支持是我持续输出技术干货的最大动力!
如果想获取更多 Java 技术深度解析,欢迎点击头像关注我,后续会每日更新高质量技术文章,陪您一起进阶成长~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。