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

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

前言


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


实现方式1: 可以自己手动创建原生的线程,实现异步。

实现方式2:也可以使用springboot的@Async实现异步。

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


1.@Async异步调用


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


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

1dc618a0ed9580ce8bfa6facb208c08f.png


2书写异步方法

5d4c6812c8535adbb050f4ddf2e1bce8.png


3.调用

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

46a9d80a6e05e4e3b19d57a0ee70bcdf.png

特别注意


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

因为@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;
    }
}


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


66ba272a0bfc97be54a5fa679e3d5482.png


备注:


可以指定线程池的前缀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异步调用深度解析
390 0
SpringBoot+@Async注解一起用,速度提升
本文介绍了异步调用在高并发Web应用性能优化中的重要性,对比了同步与异步调用的区别。同步调用按顺序执行,每一步需等待上一步完成;而异步调用无需等待,可提升效率。通过Spring Boot示例,使用@Async注解实现异步任务,并借助Future对象处理异步回调,有效减少程序运行时间。
【SpringBoot 异步魔法】@Async 注解:揭秘 SpringBoot 中异步方法的终极奥秘!
【8月更文挑战第25天】异步编程对于提升软件应用的性能至关重要,尤其是在高并发环境下。Spring Boot 通过 `@Async` 注解简化了异步方法的实现。本文详细介绍了 `@Async` 的基本用法及配置步骤,并提供了示例代码展示如何在 Spring Boot 项目中创建与管理异步任务,包括自定义线程池、使用 `CompletableFuture` 处理结果及异常情况,帮助开发者更好地理解和运用这一关键特性。
499 1
Spring容器中的bean是线程安全的吗?
Spring容器中的bean默认为单例模式,多线程环境下若操作共享成员变量,易引发线程安全问题。Spring未对单例bean做线程安全处理,需开发者自行解决。通常,Spring bean(如Controller、Service、Dao)无状态变化,故多为线程安全。若涉及线程安全问题,可通过编码或设置bean作用域为prototype解决。
61 1
|
6月前
|
spring多线程实现+合理设置最大线程数和核心线程数
本文介绍了手动设置线程池时的最大线程数和核心线程数配置方法,建议根据CPU核数及程序类型(CPU密集型或IO密集型)来合理设定。对于IO密集型,核心线程数设为CPU核数的两倍;CPU密集型则设为CPU核数加一。此外,还讨论了`maxPoolSize`、`keepAliveTime`、`allowCoreThreadTimeout`和`queueCapacity`等参数的设置策略,以确保线程池高效稳定运行。
723 11
spring多线程实现+合理设置最大线程数和核心线程数
|
8月前
|
spring boot 中默认最大线程连接数,线程池数配置查看
spring boot 中默认最大线程连接数,线程池数配置查看
452 4
|
8月前
|
Spring boot 自定义ThreadPoolTaskExecutor 线程池并进行异步操作
Spring boot 自定义ThreadPoolTaskExecutor 线程池并进行异步操作
365 3
Spring创建的单例对象,存在线程安全问题吗?
Spring框架提供了多种Bean作用域,包括单例(Singleton)、原型(Prototype)、请求(Request)、会话(Session)、全局会话(GlobalSession)等。单例是默认作用域,保证每个Spring容器中只有一个Bean实例;原型作用域则每次请求都会创建一个新的Bean实例;请求和会话作用域分别与HTTP请求和会话绑定,在Web应用中有效。 单例Bean在多线程环境中可能面临线程安全问题,Spring容器虽然确保Bean的创建过程是线程安全的,但Bean的使用安全性需开发者自行保证。保持Bean无状态是最简单的线程安全策略;
101 0
SpringBoot中@Async的实现方式探索
本文探讨了一段重写了默认`@Async`线程处理的Scala代码,并对其目的与必要性提出了疑问。原代码通过自定义`AsyncExecutorConfig`类,实现了`AsyncConfigurerSupport`接口,进而配置了一个`ThreadPoolExecutor`作为异步任务的执行器。文章随后分析了为何要替换默认实现,并对Spring框架中`@Async`注解的默认行为进行了深入研究。

热门文章

最新文章