在Spring Boot中隔离@Async异步任务的线程池

在异步任务执行的时候,我们知道其背后都有一个线程池来执行任务,但是为了控制异步任务的并发不影响到应用的正常运作,我们需要对线程池做好相关的配置,以防资源过度使用。这个时候我们就考虑将线程池进行隔离了。

那么我们为啥要隔离@Async异步任务的线程池?

  • 控制资源 :通过隔离异步任务的线程池,可以更好地控制系统的资源使用。不同类型的异步任务可能对系统资源的需求不同,例如某些任务可能需要更多的线程数或更大的队列容量。通过隔离线程池,可以为每种类型的任务分配适当的资源,避免资源争用和过度消耗。

  • 优化性能 :隔离异步任务的线程池可以帮助优化系统的性能。如果所有的异步任务共享同一个线程池,当某个任务出现阻塞或执行时间过长时,可能会影响其他任务的执行。通过隔离线程池,可以确保每个任务都有独立的线程池资源,提高系统的并发能力和响应性能。

  • 业务隔离 :有时候,不同的业务逻辑可能需要不同的异步任务处理方式。通过隔离线程池,可以为每个业务逻辑定义独立的线程池,以满足不同业务的需求。例如,某些任务可能需要更高的优先级或更短的超时时间,而另一些任务可能需要更大的线程池容量。通过隔离线程池,可以更好地管理和调整每个业务逻辑的异步任务执行环境。

下面看一个demo:

demo

  1. 创建自定义的线程池:首先,你可以创建一个自定义的线程池,用于处理@Async注解标记的异步任务。可以使用ThreadPoolTaskExecutor类来创建线程池。

    java 复制代码
    @Configuration
    @EnableAsync
    public class AsyncConfig implements AsyncConfigurer {
    
        @Bean(name = "asyncTaskExecutor")
        public Executor asyncTaskExecutor() {
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            // 配置线程池属性
            executor.setCorePoolSize(10);
            executor.setMaxPoolSize(20);
            executor.setQueueCapacity(100);
            executor.setThreadNamePrefix("AsyncTask-");
            executor.initialize();
            return executor;
        }
    
        @Override
        public Executor getAsyncExecutor() {
            return asyncTaskExecutor();
        }
    }

    在上述示例中,我们创建了一个名为asyncTaskExecutor的线程池,并配置了核心线程数、最大线程数、队列容量等属性。

  2. 在异步任务方法上指定线程池:接下来,你可以在需要异步执行的方法上使用@Async注解,并通过value属性指定要使用的线程池。

    java 复制代码
    @Service
    public class MyService {
    
       @Async("asyncTaskExecutor")
       public void asyncMethod() {
           // 异步任务的具体逻辑
       }
    }

    在上述示例中,我们使用@Async("asyncTaskExecutor")注解将asyncMethod()方法标记为异步任务,并指定了使用名为asyncTaskExecutor的线程池。

实际案例

记得在启动类中添加@EnableAsync注解呀

我们来初始化多个线程池:

java 复制代码
@EnableAsync
@Configuration
public class TaskPoolConfig {

    @Bean
    public Executor taskExecutor1() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(2);
        executor.setMaxPoolSize(2);
        executor.setQueueCapacity(10);
        executor.setKeepAliveSeconds(60);
        //使用线程名前缀,可以用来观察顺序
        executor.setThreadNamePrefix("executor-1-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return executor;
    }

    @Bean
    public Executor taskExecutor2() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(2);
        executor.setMaxPoolSize(2);
        executor.setQueueCapacity(10);
        executor.setKeepAliveSeconds(60);
        executor.setThreadNamePrefix("executor-2-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return executor;
    }
}

接下来创建一个异步任务,然后指定要使用线程池名字。

java 复制代码
@Slf4j
@Component
public class AsyncTasks {

    public static Random random = new Random();

    @Async("taskExecutor1")
    public CompletableFuture<String> doTaskOne(String taskNo) throws Exception {
        log.info("开始任务:{}", taskNo);
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("完成任务:{},耗时:{} 毫秒", taskNo, end - start);
        return CompletableFuture.completedFuture("任务完成");
    }

    @Async("taskExecutor2")
    public CompletableFuture<String> doTaskTwo(String taskNo) throws Exception {
        log.info("开始任务:{}", taskNo);
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("完成任务:{},耗时:{} 毫秒", taskNo, end - start);
        return CompletableFuture.completedFuture("任务完成");
    }

}

创建一个测试类:

java 复制代码
@Slf4j
@SpringBootTest
public class ApplicationTests {

    @Autowired
    private AsyncTasks asyncTasks;

    @Test
    public void test() throws Exception {
        long start = System.currentTimeMillis();

        // 线程池1
        CompletableFuture<String> task1 = asyncTasks.doTaskOne("1");
        CompletableFuture<String> task2 = asyncTasks.doTaskOne("2");
        CompletableFuture<String> task3 = asyncTasks.doTaskOne("3");

        // 线程池2
        CompletableFuture<String> task4 = asyncTasks.doTaskTwo("4");
        CompletableFuture<String> task5 = asyncTasks.doTaskTwo("5");
        CompletableFuture<String> task6 = asyncTasks.doTaskTwo("6");

        // 一起执行
        CompletableFuture.allOf(task1, task2, task3, task4, task5, task6).join();

        long end = System.currentTimeMillis();

        log.info("任务全部完成,总耗时:" + (end - start) + "毫秒");
    }

}

在上面的单元测试中,一共启动了6个异步任务,前三个用的是线程池1,后三个用的是线程池2。

先不执行,根据设置的核心线程2和最大线程数2,我们来猜猜线程的执行顺序。

  • 线程池1的三个任务,task1和task2会先获得执行线程,然后task3因为没有可分配线程进入缓冲队列

  • 线程池2的三个任务,task4和task5会先获得执行线程,然后task6因为没有可分配线程进入缓冲队列

  • 任务task3会在task1或task2完成之后,开始执行

  • 任务task6会在task4或task5完成之后,开始执行

执行结果:

通过以上步骤,你可以实现对@Async异步任务的线程池进行隔离。这样可以根据需要创建多个线程池,并为不同的异步任务指定不同的线程池,以实现任务之间的隔离和资源控制。通过隔离@Async异步任务的线程池,可以实现对系统资源的控制、性能的优化和业务逻辑的隔离。这样可以提高系统的稳定性、可伸缩性和灵活性,更好地满足不同业务场景下的需求。

相关推荐
H_unique几秒前
LangChain:创建工具Ⅰ
python·langchain
Ruci ALYS6 分钟前
SpringBoot Maven快速上手
spring boot·后端·maven
eqwaak09 分钟前
PyTorch张量操作全攻略:从入门到精通
开发语言·人工智能·pytorch·python
傻瓜搬砖人10 分钟前
SpringMVC的请求
java·前端·javascript·spring
A懿轩A13 分钟前
Ghostty:告别 Mac 毛坯终端,打造 2026 最丝滑的 Ghostty AI 开发驾驶舱——Claude Code 团队也在用
python·macos·策略模式
Chasing Aurora14 分钟前
python 安装依赖和导入模块 详解
开发语言·python·虚拟环境·import·pyenv·requirements
念恒1230617 分钟前
Python(for循环)
python·学习
咱那飘逸的长发19 分钟前
Trae java项目配置全局maven和jdk
java·python·maven
爱上好庆祝24 分钟前
学习js的第六天(js基础的结束)
开发语言·前端·javascript·学习·ecmascript
码界筑梦坊28 分钟前
116-基于Flask的健身房会员锻炼数据可视化分析系统
python·信息可视化·数据分析·flask·毕业设计