在Java并发编程中,线程池是一种非常重要的资源管理工具,它允许我们在应用程序中有效地管理和重用线程,从而提高性能并降低资源消耗。特别是在SpringBoot等企业级应用中,正确使用线程池对于应用程序的稳定性和性能至关重要。
根据阿里巴巴《Java开发手册》中的强制要求:
【强制要求】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors返回的线程池对象的弊端如下:
1) FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
2)CachedThreadPool:允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
本文将详细介绍线程池的基本原理、常用的工作队列类型及其优缺点,以及在SpringBoot中的简单实现方式。
线程池的核心思想是复用线程,避免频繁创建和销毁线程所带来的性能开销。它的工作流程如下:
┌─────────────────┐ ┌───────────────┐ ┌─────────────────┐
│ │ │ │ │ │
│ corePoolSize │─────────│ workQueue │─────────│ maximumPoolSize│
│ 核心线程数 │ │ 工作队列 │ │ 最大线程数 │
└─────────────────┘ └───────────────┘ └─────────────────┘
│ │ │
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌───────────────┐ ┌─────────────────┐
│ 创建新线程执行 │ │ 放入工作队列 │ │ 创建新线程执行 │
└─────────────────┘ └───────────────┘ └─────────────────┘
│
│
▼
┌─────────────────┐
│ 拒绝策略 │
└─────────────────┘
两次创建线程对比:
对比维度 | 第一次创建(核心线程) | 第二次创建(非核心线程) |
---|---|---|
触发条件 | 线程数 < corePoolSize |
线程数 ≥ corePoolSize 且 队列已满 |
线程性质 | 核心线程,默认长期存活 | 临时线程,空闲超时后被回收 |
目的 | 维持基础并发能力 | 应对突发流量,防止队列积压 |
是否受keepAliveTime 影响 |
默认否(需设置allowCoreThreadTimeOut=true ) |
是 |
ThreadPoolExecutor
构造函数有7个参数:
public ThreadPoolExecutor(
int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 空闲线程存活时间
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 工作队列
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler // 拒绝策略
)
选择合适的工作队列对线程池的性能影响很大。以下是常用的几种队列类型及其优缺点:
基于数组的有界阻塞队列,按FIFO(先进先出)原则对元素进行排序。
优点:
缺点:
基于链表的阻塞队列,按FIFO原则对元素进行排序。
优点:
缺点:
不存储元素的阻塞队列,每个插入操作必须等待另一个线程的删除操作。
优点:
缺点:
具有优先级的无界阻塞队列,元素按优先级顺序出队。
优点:
缺点:
延迟队列,元素只有到了指定的延迟时间才能被取出。
优点:
缺点:
在SpringBoot应用中配置线程池有多种方式,下面介绍几种常用的方法:
@Configuration
public class ThreadPoolConfig {
@Bean
public ThreadPoolExecutor threadPoolExecutor(
@Value("${thread.pool.corePoolSize:10}") int corePoolSize,
@Value("${thread.pool.maxPoolSize:20}") int maxPoolSize,
@Value("${thread.pool.queueCapacity:200}") int queueCapacity,
@Value("${thread.pool.keepAliveSeconds:60}") int keepAliveSeconds) {
// 使用有界队列
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(queueCapacity);
// 自定义线程工厂
ThreadFactory threadFactory = new ThreadFactoryBuilder()
.setNameFormat("业务处理线程-%d")
.setDaemon(false)
.setPriority(Thread.NORM_PRIORITY)
.build();
return new ThreadPoolExecutor(
corePoolSize,
maxPoolSize,
keepAliveSeconds,
TimeUnit.SECONDS,
workQueue,
threadFactory,
new ThreadPoolExecutor.CallerRunsPolicy());
}
}
@Configuration
public class ThreadPoolConfig {
@Bean
public ThreadPoolTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心线程数
executor.setCorePoolSize(10);
// 最大线程数
executor.setMaxPoolSize(20);
// 队列容量
executor.setQueueCapacity(200);
// 线程最大空闲时间
executor.setKeepAliveSeconds(60);
// 线程名前缀
executor.setThreadNamePrefix("taskExecutor-");
// 拒绝策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 等待所有任务完成后再关闭线程池
executor.setWaitForTasksToCompleteOnShutdown(true);
// 等待终止的时间
executor.setAwaitTerminationSeconds(60);
executor.initialize();
return executor;
}
}
首先配置异步执行的线程池:
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean("asyncExecutor")
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(200);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("Async-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
然后在需要异步执行的方法上添加@Async注解:
@Service
public class EmailService {
@Async("asyncExecutor")
public CompletableFuture<Boolean> sendEmail(String to, String subject, String content) {
// 发送邮件的耗时操作
return CompletableFuture.completedFuture(Boolean.TRUE);
}
}
在需要处理大量数据的场景中,可以使用线程池进行并行处理:
@Service
public class BatchProcessService {
@Autowired
private ThreadPoolTaskExecutor taskExecutor;
public void processBatch(List<Data> dataList) {
// 分批处理
int batchSize = 100;
for (int i = 0; i < dataList.size(); i += batchSize) {
List<Data> batch = dataList.subList(i, Math.min(i + batchSize, dataList.size()));
taskExecutor.submit(() -> processBatchInternal(batch));
}
}
private void processBatchInternal(List<Data> batch) {
// 处理单个批次的数据
batch.forEach(data -> {
// 处理单条数据
});
}
}
在完成某些操作后需要进行异步通知时:
@Service
public class NotificationService {
@Autowired
private ThreadPoolExecutor threadPoolExecutor;
public void sendNotifications(List<String> userIds, String message) {
for (String userId : userIds) {
threadPoolExecutor.execute(() -> {
try {
// 发送通知
System.out.println("向用户 " + userId + " 发送通知: " + message);
} catch (Exception e) {
// 错误处理
System.err.println("发送通知失败: " + e.getMessage());
}
});
}
}
}
结合SpringBoot的@Scheduled注解使用自定义线程池:
@Configuration
@EnableScheduling
public class ScheduleConfig implements SchedulingConfigurer {
@Autowired
private ThreadPoolTaskExecutor taskExecutor;
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(scheduledTaskExecutor());
}
@Bean(destroyMethod = "shutdown")
public Executor scheduledTaskExecutor() {
return Executors.newScheduledThreadPool(10, r -> {
Thread t = new Thread(r);
t.setName("scheduled-task-" + t.getId());
return t;
});
}
}
@Component
public class DataSyncTask {
@Autowired
private ThreadPoolTaskExecutor taskExecutor;
@Scheduled(cron = "0 0/30 * * * ?") // 每30分钟执行一次
public void syncData() {
System.out.println("开始数据同步任务...");
// 获取需要同步的数据列表
List<String> dataIds = getDataIdsToSync();
// 使用线程池并行处理
for (String dataId : dataIds) {
taskExecutor.submit(() -> syncSingleData(dataId));
}
}
private List<String> getDataIdsToSync() {
// 获取需要同步的数据ID列表
return Arrays.asList("data1", "data2", "data3");
}
private void syncSingleData(String dataId) {
try {
System.out.println("同步数据: " + dataId);
// 具体同步逻辑...
} catch (Exception e) {
System.err.println("数据同步失败: " + e.getMessage());
}
}
}
在生产环境中,监控线程池的运行状态是非常重要的,可以帮助我们及时发现问题并进行调整。
@Component
@RequiredArgsConstructor
public class ThreadPoolMonitor {
private final ThreadPoolExecutor threadPoolExecutor;
private final ThreadPoolTaskExecutor taskExecutor;
@Scheduled(fixedRate = 60000) // 每分钟记录一次
public void monitorThreadPool() {
ThreadPoolExecutor executor = threadPoolExecutor;
logThreadPoolStatus("自定义线程池", executor);
// 监控ThreadPoolTaskExecutor
ThreadPoolExecutor tpExecutor = taskExecutor.getThreadPoolExecutor();
logThreadPoolStatus("任务执行线程池", tpExecutor);
}
private void logThreadPoolStatus(String poolName, ThreadPoolExecutor executor) {
int activeCount = executor.getActiveCount(); // 活跃线程数
int poolSize = executor.getPoolSize(); // 当前线程数
int corePoolSize = executor.getCorePoolSize(); // 核心线程数
int maximumPoolSize = executor.getMaximumPoolSize(); // 最大线程数
long completedTaskCount = executor.getCompletedTaskCount(); // 已完成任务数
long taskCount = executor.getTaskCount(); // 总任务数
int queueSize = executor.getQueue().size(); // 队列大小
log.info(
"线程池状态 [{}]: 活跃线程数={}, 线程池大小={}, 核心线程数={}, " +
"最大线程数={}, 已完成任务数={}, 总任务数={}, 队列中任务数={}, 队列剩余容量={}",
poolName, activeCount, poolSize, corePoolSize, maximumPoolSize,
completedTaskCount, taskCount, queueSize,
(executor.getQueue() instanceof LinkedBlockingQueue)
? ((LinkedBlockingQueue<?>) executor.getQueue()).remainingCapacity()
: -1
);
// 计算线程池利用率
double utilizationRate = (double) activeCount / poolSize;
log.info("线程池 [{}] 利用率: {}", poolName,
String.format("%.2f%%", utilizationRate * 100));
// 监控任务队列使用情况
if (executor.getQueue() instanceof LinkedBlockingQueue) {
LinkedBlockingQueue<?> queue = (LinkedBlockingQueue<?>) executor.getQueue();
int capacity = queue.size() + queue.remainingCapacity();
double queueUsageRate = (double) queueSize / capacity;
log.info("队列 [{}] 使用率: {}", poolName,
String.format("%.2f%%", queueUsageRate * 100));
}
// 任务拒绝情况监控(需要自定义RejectedExecutionHandler来记录拒绝次数)
if (executor.getRejectedExecutionHandler() instanceof MonitoredRejectedExecutionHandler) {
MonitoredRejectedExecutionHandler handler =
(MonitoredRejectedExecutionHandler) executor.getRejectedExecutionHandler();
log.info("线程池 [{}] 任务拒绝次数: {}", poolName, handler.getRejectedCount());
}
}
// 自定义的拒绝策略处理器,增加了拒绝次数的记录
public static class MonitoredRejectedExecutionHandler implements RejectedExecutionHandler {
private final RejectedExecutionHandler delegate;
private final AtomicLong rejectedCount = new AtomicLong(0);
public MonitoredRejectedExecutionHandler(RejectedExecutionHandler delegate) {
this.delegate = delegate;
}
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
rejectedCount.incrementAndGet();
delegate.rejectedExecution(r, executor);
}
public long getRejectedCount() {
return rejectedCount.get();
}
}
}
合理配置线程池参数是很重要的,以下是一些经验法则:
核心线程数的选择:
// 获取CPU核心数
int processors = Runtime.getRuntime().availableProcessors();
// CPU密集型任务
int corePoolSize = processors + 1;
// IO密集型任务
int ioPoolSize = processors * 2;
队列容量的选择:
拒绝策略的选择:
问题:任务执行速度慢,导致队列中堆积了大量任务。
解决方案:
问题:经常有任务被拒绝执行。
解决方案:
问题:使用无界队列导致内存溢出。
解决方案:
线程池是Java并发编程中非常重要的工具,正确使用线程池可以提高应用程序的性能和稳定性。在SpringBoot应用中,我们应该遵循阿里巴巴Java开发手册的建议,避免使用Executors创建线程池,而是通过ThreadPoolExecutor明确指定各项参数。
选择合适的工作队列类型、设置合理的线程数量和队列容量,以及实现适当的拒绝策略,这些都是使用线程池时需要考虑的关键因素。通过本文介绍的简单配置方法,你可以在SpringBoot应用中轻松实现一个高效且安全的线程池。