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 分钟,避免无限阻塞)
        CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0]))
                .get(10, TimeUnit.MINUTES);

        return "100 个文档已全部处理完成!";
    }
}
 

2. 优势

  • 可通过 CompletableFuture 的 whenComplete() 处理单个任务的成功 / 失败回调;
  • 支持 allOf()(等待所有任务完成)或 anyOf()(等待任一任务完成);
  • 可设置超时时间,避免任务无限阻塞;
  • 并发数由线程池统一控制,避免线程泛滥。

五、进阶:动态调整线程池参数(生产环境推荐)

生产环境中,线程池参数可能需要根据负载动态调整,可结合 Spring Boot 的 @ConfigurationProperties 实现配置化,无需修改代码即可调整参数:

1. 配置文件(application.yml)

yaml
 
 
# 线程池动态配置
thread-pool:
  core-pool-size: 8
  max-pool-size: 16
  queue-capacity: 200
  keep-alive-seconds: 60
 

2. 调整配置类

java
 
运行
 
 
 
 
import org.springframework.boot.context.properties.ConfigurationProperties;
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
@ConfigurationProperties(prefix = "thread-pool") // 绑定配置文件前缀
public class DynamicThreadPoolConfig {

    // 从配置文件读取参数
    private int corePoolSize;
    private int maxPoolSize;
    private int queueCapacity;
    private int keepAliveSeconds;

    // Getter + Setter(必须有,否则无法绑定配置)
    public int getCorePoolSize() { return corePoolSize; }
    public void setCorePoolSize(int corePoolSize) { this.corePoolSize = corePoolSize; }
    public int getMaxPoolSize() { return maxPoolSize; }
    public void setMaxPoolSize(int maxPoolSize) { this.maxPoolSize = maxPoolSize; }
    public int getQueueCapacity() { return queueCapacity; }
    public void setQueueCapacity(int queueCapacity) { this.queueCapacity = queueCapacity; }
    public int getKeepAliveSeconds() { return keepAliveSeconds; }
    public void setKeepAliveSeconds(int keepAliveSeconds) { this.keepAliveSeconds = keepAliveSeconds; }

    @Bean(name = "taskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(maxPoolSize);
        executor.setQueueCapacity(queueCapacity);
        executor.setKeepAliveSeconds(keepAliveSeconds);
        executor.setThreadNamePrefix("Async-Task-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}
 

六、注意事项

  1. 线程池选择:Spring Boot 推荐用 ThreadPoolTaskExecutor(对 ThreadPoolExecutor 的封装,更适配 Spring 生态);
  2. 避免线程泄漏:
    • 异步任务中避免创建无限循环的线程;
    • Semaphore 的 acquire() 必须对应 release()(放在 finally 中);
  3. IO 密集型 vs CPU 密集型:
    • CPU 密集型任务(如计算):核心线程数 = CPU 核心数 + 1(避免上下文切换过多);
    • IO 密集型任务(如文件读写、API 调用):核心线程数可设更大(如 CPU*5),因为线程大部分时间在等待 IO;
  4. 监控线程池:生产环境可通过 Spring Boot Actuator 暴露线程池指标(如活跃线程数、队列长度),便于监控和调优。
通过以上方案,可灵活实现 Spring Boot 中的并发线程控制,适配异步任务、接口限流、批量处理等多种场景。
posted @ 2025-11-21 14:33  hanease  阅读(0)  评论(0)    收藏  举报