使用spring boot的@Async实现异步调用和线程池复用

简介: 使用spring boot的@Async实现异步调用和线程池复用


前言

我们在开发中经常会对执行慢的方法或不涉及主业务的方法执行异步调用。

  • 实现方式1: 可以自己手动创建原生的线程,实现异步。
  • 实现方式2:也可以使用springboot的@Async实现异步。

本文主要说下springboot的@Async方式和多线程时如何自定义线程池,以及希望有返回结果时,如何用其内置的方法为我所用。

1.@Async异步调用

异步的原理: springboot会为代理对象创建一个线程,执行异步方法。

  • 1.在springboot的入口函数处引入 开启异步自动配置注解@EnableAsync

  • 2书写异步方法

  • 3.调用
    在需要用到异步调用的地方,调用异步方法

特别注意

异步方法不可和调用它的类在一个类中,

因为@Async是springboot使用的代理对象来创建或者使用线程池中的线程处理,

如果在一个类中就成同步了。这点和不能调用同一个类的带有@Transactional方法是一个道理。

2.手动创建线程池管理@Async异步的线程

如果不手动创建线程池,springboot会用自己的线程池来处理,不过为了更好的控制线程,我们可以手动创建。springboot也给我们提供了配置方法。

我们可以这样配置。

@Configuration
@EnableAsync
public class ThreadPoolTaskConfig {
    /**
     * 核心线程数(默认线程数)
     */
    private static final int corePoolSize = 10;
    /**
     * 最大线程数
     */
    private static final int maxPoolSize = 50;
    /**
     * 允许线程空闲时间(单位:默认为秒)
     */
    private static final int keepAliveTime = 5;
    /**
     * 缓冲队列大小
     */
    private static final int queueCapacity = 200;
    /**
     * 线程池名前缀
     */
    private static final String threadNamePrefix = "Async-Service-";
    @Bean("taskExecutor") // bean的名称,默认为首字母小写的方法名
    public ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(maxPoolSize);
        executor.setQueueCapacity(queueCapacity);
        executor.setKeepAliveSeconds(keepAliveTime);
        executor.setThreadNamePrefix(threadNamePrefix);
        // 线程池对拒绝任务的处理策略
        // CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 初始化
        executor.initialize();
        return executor;
    }
}

然后在用到的异步方法的地方,显式引用改线程池。

备注:

可以指定线程池的前缀threadNamePrefix,便于以后分析查看日志。如果需要创建多个线程池,指定不同的前缀,就可以快速定位出现问题的代码。

3.异步创建有返回值的调用

有时候我们不止希望异步执行任务,还希望任务执行完成后会有一个返回值,在java中提供了Future泛型接口,用来接收任务执行结果,springboot也提供了此类支持,使用实现了ListenableFuture接口的类如AsyncResult来作为返回值的载体。比如上例中,我们希望返回一个类型为String类型的值,可以将返回值改造为:

异步任务类

@Component  
public class AsyncTask {  
    @Async  
    public Future<String> task1() throws InterruptedException{  
        long currentTimeMillis = System.currentTimeMillis();  
        Thread.sleep(1000);  
        long currentTimeMillis1 = System.currentTimeMillis();  
        System.out.println("task1任务耗时:"+(currentTimeMillis1-currentTimeMillis)+"ms");  
        return new AsyncResult<String>("task1执行完毕");  
    }  
    @Async  
    public Future<String> task2() throws InterruptedException{  
        long currentTimeMillis = System.currentTimeMillis();  
        Thread.sleep(2000);  
        long currentTimeMillis1 = System.currentTimeMillis();  
        System.out.println("task2任务耗时:"+(currentTimeMillis1-currentTimeMillis)+"ms");  
        return new AsyncResult<String>("task2执行完毕");  
    }  
    @Async  
    public Future<String> task3() throws InterruptedException{  
        long currentTimeMillis = System.currentTimeMillis();  
        Thread.sleep(3000);  
        long currentTimeMillis1 = System.currentTimeMillis();  
        System.out.println("task3任务耗时:"+(currentTimeMillis1-currentTimeMillis)+"ms");  
        return new AsyncResult<String>("task3执行完毕");  
    }  
}

任务返回后使用:

@RequestMapping("")  
@RestController  
public class AsyncTaskController {  
    @Autowired  
    private AsyncTask asyncTask;  
    @RequestMapping("")  
    public String doTask() throws InterruptedException{  
        long currentTimeMillis = System.currentTimeMillis();  
        Future<String> task1 = asyncTask.task1();  
        Future<String> task2 = asyncTask.task2();  
        Future<String> task3 = asyncTask.task3();  
        String result = null;  
        for (;;) {  
            if(task1.isDone() && task2.isDone() && task3.isDone()) {  
                // 三个任务都调用完成,退出循环等待  
                break;  
            }  
            Thread.sleep(1000);  
        }  
        long currentTimeMillis1 = System.currentTimeMillis();  
        result = "task任务总耗时:"+(currentTimeMillis1-currentTimeMillis)+"ms";  
        return result;  
    }  
}

大功告成,完!

目录
打赏
0
0
0
0
15
分享
相关文章
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
510 0
Spring容器中的bean是线程安全的吗?
Spring容器中的bean默认为单例模式,多线程环境下若操作共享成员变量,易引发线程安全问题。Spring未对单例bean做线程安全处理,需开发者自行解决。通常,Spring bean(如Controller、Service、Dao)无状态变化,故多为线程安全。若涉及线程安全问题,可通过编码或设置bean作用域为prototype解决。
93 1
|
9月前
|
spring多线程实现+合理设置最大线程数和核心线程数
本文介绍了手动设置线程池时的最大线程数和核心线程数配置方法,建议根据CPU核数及程序类型(CPU密集型或IO密集型)来合理设定。对于IO密集型,核心线程数设为CPU核数的两倍;CPU密集型则设为CPU核数加一。此外,还讨论了`maxPoolSize`、`keepAliveTime`、`allowCoreThreadTimeout`和`queueCapacity`等参数的设置策略,以确保线程池高效稳定运行。
1013 11
spring多线程实现+合理设置最大线程数和核心线程数
|
9月前
|
运行@Async注解的方法的线程池
自定义@Async注解线程池
267 3
|
11月前
|
spring boot 中默认最大线程连接数,线程池数配置查看
spring boot 中默认最大线程连接数,线程池数配置查看
500 4
|
11月前
|
Spring boot 自定义ThreadPoolTaskExecutor 线程池并进行异步操作
Spring boot 自定义ThreadPoolTaskExecutor 线程池并进行异步操作
485 3
|
10月前
|
Spring创建的单例对象,存在线程安全问题吗?
Spring框架提供了多种Bean作用域,包括单例(Singleton)、原型(Prototype)、请求(Request)、会话(Session)、全局会话(GlobalSession)等。单例是默认作用域,保证每个Spring容器中只有一个Bean实例;原型作用域则每次请求都会创建一个新的Bean实例;请求和会话作用域分别与HTTP请求和会话绑定,在Web应用中有效。 单例Bean在多线程环境中可能面临线程安全问题,Spring容器虽然确保Bean的创建过程是线程安全的,但Bean的使用安全性需开发者自行保证。保持Bean无状态是最简单的线程安全策略;
122 0
Spring Boot 优雅关机时异步线程安全优化
Spring Boot 优雅关机时异步线程安全优化
244 1
Spring Boot优雅Shutdown时异步线程安全优化
Spring Boot优雅Shutdown时异步线程安全优化
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等