springboot 控制并发线程的代码
springboot 控制并发线程的代码
在 Spring Boot 中控制并发线程,核心是通过 线程池(ThreadPoolExecutor) 管理任务执行,结合 Spring 提供的注解或原生 API 实现限流、异步任务并发控制。以下是 3 种常用场景的完整实现方案,覆盖异步任务、接口限流、批量任务并发控制:
一、基础:配置全局线程池(核心依赖)
Spring Boot 中推荐用
@Configuration 配置线程池,统一管理所有异步任务的并发数(核心线程数、最大线程数、队列容量等)。1. 线程池配置类
java
运行
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* 全局线程池配置(控制异步任务并发)
*/
@Configuration
@EnableAsync // 开启 Spring 异步任务支持
public class ThreadPoolConfig {
/**
* 核心线程数:线程池常驻的最小线程数(默认=CPU核心数+1)
*/
private static final int CORE_POOL_SIZE = Runtime.getRuntime().availableProcessors() + 1;
/**
* 最大线程数:线程池可创建的最大线程数(默认=CPU核心数*2+1)
*/
private static final int MAX_POOL_SIZE = Runtime.getRuntime().availableProcessors() * 2 + 1;
/**
* 队列容量:任务等待队列的长度(超过核心线程数后,任务先入队,队列满了才创建新线程)
*/
private static final int QUEUE_CAPACITY = 100;
/**
* 空闲线程存活时间:超过核心线程数的空闲线程,多久后销毁(单位:秒)
*/
private static final int KEEP_ALIVE_SECONDS = 60;
/**
* 线程池 Bean(名称:taskExecutor,供 @Async 注解引用)
*/
@Bean(name = "taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(CORE_POOL_SIZE);
executor.setMaxPoolSize(MAX_POOL_SIZE);
executor.setQueueCapacity(QUEUE_CAPACITY);
executor.setKeepAliveSeconds(KEEP_ALIVE_SECONDS);
executor.setThreadNamePrefix("Async-Task-"); // 线程名称前缀(便于日志排查)
// 队列满且达到最大线程数时,任务的拒绝策略(可选 4 种)
// 1. AbortPolicy(默认):直接抛出 RejectedExecutionException 异常
// 2. CallerRunsPolicy:由提交任务的主线程执行(避免任务丢失,适合低并发)
// 3. DiscardPolicy:直接丢弃任务(不抛异常)
// 4. DiscardOldestPolicy:丢弃队列中最旧的任务,再尝试提交当前任务
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 初始化线程池
executor.initialize();
return executor;
}
}
2. 关键参数说明(根据业务调整)
| 参数 | 作用 | 推荐配置原则 |
|---|---|---|
| 核心线程数(corePoolSize) | 常驻线程数,避免频繁创建销毁线程 | CPU 核心数 + 1(CPU 密集型);IO 密集型可设更大(如 CPU*5) |
| 最大线程数(maxPoolSize) | 线程池上限,防止线程过多导致 OOM | CPU 核心数2+1(CPU 密集型);IO 密集型可设 CPU10 |
| 队列容量(queueCapacity) | 任务等待队列,缓冲超过核心线程的任务 | 结合业务 QPS 设置(如 100-1000,避免队列过长导致内存溢出) |
| 拒绝策略(RejectedExecutionHandler) | 队列 + 最大线程数都满时的处理方式 | 生产环境推荐 CallerRunsPolicy 或自定义拒绝策略(如记录日志 + 重试) |
二、场景 1:异步任务并发控制(@Async 注解)
通过
@Async 注解标记方法,Spring 会自动将方法提交到上面配置的线程池执行,无需手动创建线程。1. 异步任务服务类
java
运行
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
/**
* 异步任务服务(并发由线程池控制)
*/
@Service
public class AsyncTaskService {
/**
* 异步执行的任务(指定线程池:taskExecutor)
* 注:@Async 注解的方法不能和调用方在同一个类中(否则AOP代理失效,无法异步执行)
*/
@Async("taskExecutor")
public void executeAsyncTask(Long taskId) {
try {
// 模拟业务逻辑(如处理案件报告导出、数据同步等)
System.out.println("线程 " + Thread.currentThread().getName() + " 执行任务:" + taskId);
Thread.sleep(1000); // 模拟耗时操作
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
}
}
2. 测试:批量提交异步任务
java
运行
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 测试异步任务并发
*/
@RestController
public class AsyncTestController {
@Autowired
private AsyncTaskService asyncTaskService;
/**
* 一次性提交 20 个异步任务,线程池自动控制并发数
*/
@GetMapping("/test/async")
public String testAsync() {
// 提交 20 个任务(超过核心线程数后,任务先入队,队列满了再扩容线程)
for (long i = 1; i <= 20; i++) {
asyncTaskService.executeAsyncTask(i);
}
return "所有任务已提交,线程池正在并发执行!";
}
}
3. 效果
- 线程池核心线程数若为 8(假设 CPU 核心数为 7),则初始会有 8 个线程同时执行任务;
- 第 9-108 个任务会进入队列等待;
- 队列满(100 个)后,会创建新线程(最多 15 个),超过 15 个线程 + 100 个队列的任务,会触发拒绝策略。
三、场景 2:接口并发限流(控制同一接口的并发请求数)
如果需要限制 单个接口的最大并发请求数(而非全局异步任务),可结合
Semaphore(信号量)实现。Semaphore 相当于 “并发许可证”,只有拿到许可证的请求才能执行。1. 接口限流实现(基于 Semaphore)
java
运行
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.Semaphore;
/**
* 接口并发限流示例(限制最大并发请求数为 10)
*/
@RestController
public class LimitController {
/**
* 信号量:允许的最大并发数为 10(可根据业务调整)
* 注:若需全局共享,建议用 @Bean 注入(避免多实例重复创建)
*/
private final Semaphore semaphore = new Semaphore(10);
/**
* 被限流的接口(如案件报告查询导出接口)
*/
@GetMapping("/case/export")
public String exportCaseReport() {
// 尝试获取许可证(默认非公平锁,可传 true 改为公平锁)
// acquire() 会阻塞,直到拿到许可证;tryAcquire() 非阻塞,拿不到直接返回 false
try {
semaphore.acquire(); // 阻塞等待许可证
// 核心业务逻辑:查询案件数据 + 生成报告 + 导出
System.out.println("接口并发数:" + (10 - semaphore.availablePermits()));
Thread.sleep(2000); // 模拟耗时操作
return "案件报告导出成功!";
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return "请求被中断!";
} finally {
semaphore.release(); // 释放许可证(必须在 finally 中执行,避免泄漏)
}
}
/**
* 非阻塞版(拿不到许可证直接返回“系统繁忙”)
*/
@GetMapping("/case/export/non-block")
public String exportCaseReportNonBlock() {
if (!semaphore.tryAcquire()) { // 尝试获取许可证,1秒内拿不到则返回
return "系统繁忙,请稍后再试!";
}
try {
// 核心业务逻辑
System.out.println("接口并发数:" + (10 - semaphore.availablePermits()));
Thread.sleep(2000);
return "案件报告导出成功!";
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return "请求被中断!";
} finally {
semaphore.release();
}
}
}
2. 关键说明
Semaphore(10)表示最多允许 10 个请求同时执行接口逻辑;acquire():阻塞等待许可证(适合允许用户等待的场景,如导出大文件);tryAcquire():非阻塞(适合快速响应场景,如普通查询接口,避免用户长时间等待);- 必须在
finally中调用release(),否则许可证会泄漏,导致后续请求无法执行。
四、场景 3:批量任务并发控制(CompletableFuture 手动控制)
如果需要手动控制批量任务的并发(如批量处理几百 G 文档的拆分文件),可结合
CompletableFuture + 线程池实现,灵活控制任务提交和结果汇总。1. 批量任务并发处理示例
java
运行
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
/**
* 批量任务并发控制(如批量处理拆分后的文档)
*/
@RestController
public class BatchTaskController {
// 注入前面配置的线程池
@Autowired
private ThreadPoolTaskExecutor taskExecutor;
/**
* 批量处理任务(如 100 个文档文件,并发执行)
*/
@GetMapping("/batch/process")
public String batchProcess() throws Exception {
// 模拟 100 个任务(如 100 个拆分后的文档路径)
List<String> taskList = new ArrayList<>();
for (int i = 1; i <= 100; i++) {
taskList.add("doc-" + i + ".pdf");
}
// 存储所有异步任务的 CompletableFuture
List<CompletableFuture<Void>> futureList = new ArrayList<>();
// 提交所有任务到线程池,并发执行
for (String docPath : taskList) {
// 异步执行单个任务
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
// 核心业务:处理单个文档(如调用 DeepSeek API 分析)
System.out.println("线程 " + Thread.currentThread().getName() + " 处理文档:" + docPath);
try {
Thread.sleep(1500); // 模拟文档处理耗时
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, taskExecutor); // 指定线程池
futureList.add(future);
}
// 等待所有任务完成(最多等待 10 分钟,避免无限阻塞)
