在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异步任务的线程池,可以实现对系统资源的控制、性能的优化和业务逻辑的隔离。这样可以提高系统的稳定性、可伸缩性和灵活性,更好地满足不同业务场景下的需求。

相关推荐
志尊宝6 小时前
AI原生前端开发(041):AI辅助的状态管理(Zustand/Pinia)
前端·javascript·ai-native
修炼的dance6 小时前
pngquant 两档质量后接 oxipng,每步省多少花几秒
前端
费曼学习法6 小时前
无人值守AI内容矩阵实战(10):Windows 无 systemd,进程存活率 99% 怎么做
前端
第404号工位6 小时前
离职退电脑前别只点“清空回收站”!手把手教你彻底抹除凭证与数字痕迹
linux·python·架构
小兔子6 小时前
Integer 的 == 在 100 时是 true,在 1000 时是 false
前端
Metaphor6926 小时前
使用 Python 在 Word 文档中添加、修改和删除变量
python·word
尘世中一位迷途小书童6 小时前
和 AI 打交道会撞见的一整串词:从大模型到 Agent、RAG、MCP、Skill,一次串清
前端·人工智能
終不似少年遊*6 小时前
【AI Coding实战方法论】从计划协作到架构护栏,一套能长期复用的工程流程
人工智能·python·团队开发·ai编程·ai coding·skills·上下文管理
lusklusklusk6 小时前
Python 全局变量与局部变量区别——global 关键字、__main__ 块与作用域示例
开发语言·前端·python
马剑威(威哥爱编程)6 小时前
Python 智能体开发实战 01:装了三遍环境还在报错?虚拟环境和 DeepSeek 一次配到位
开发语言·人工智能·python·agent