@Async异步线程:Spring 自带的异步解决方案

这篇具有很好参考价值的文章主要介绍了@Async异步线程:Spring 自带的异步解决方案。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

@Async异步线程:Spring 自带的异步解决方案

前言 

        在项目应用中,使用MQ异步调用来实现系统性能优化,完成服务间数据同步是常用的技术手段。如果是在同一台服务器内部,不涉及到分布式系统,单纯的想实现部分业务的异步执行,这里介绍一个更简单的异步方法调用。

        对于异步方法调用,从Spring3 开始提供了@Async 注解,该注解可以被标注在方法上,以便异步地调用该方法。调用者将在调用时立即返回,而方法的实际执行将提交给 Spring TaskExecutor 的任务中,由指定的线程池中的线程执行。

        本文讲述了@Async注解在Spring体系中的简单应用,仅供学习,欢迎意见反馈。  


正文

一、Spring线程池的分类

        以下是官方已经实现的常见的5个TaskExecuter。Spring 宣称对于任何场景,这些TaskExecuter完全够用了:

线程 特点
SimpleAsyncTaskExecutor 每次请求新开线程,没有最大线程数设置.不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程
SyncTaskExecutor 不是异步的线程。同步可以用SyncTaskExecutor,但这个可以说不算一个线程池,因为还在原线程执行。这个类没有实现异步调用,只是一个同步操作。
ConcurrentTaskExecutor Executor的适配类,不推荐使用。如果ThreadPoolTaskExecutor不满足要求时,才用考虑使用这个类。
SimpleThreadPoolTaskExecutor 是Quartz的SimpleThreadPool的类。线程池同时被quartz和非quartz使用,才需要使用此类。
ThreadPoolTaskExecutor 最常使用,推荐,是阿里巴巴Java开发规范中指定的线程类,要求jdk版本大于等于5。其实质是对java.util.concurrent.ThreadPoolExecutor 的包装。

       参考阿里巴巴java开发规范, 在线程池应用中:线程池不允许使用Executors去创建,也不允许使用系统默认的线程池,推荐通过 ThreadPoolExecutor 的方式,这样的处理方式让开发的工程师更加明确线程池的运行规则,规避资源耗尽的风险。

二、SpringBoot中使用@Async

        使用异步线程调用方法,过程如下:

  • 编写配置类,定义线程池
  • 启动类/配置文件上加上注解:@EnableAsync
  • 方法上加上注解:@Async

        下面演示案例中,我本地项目的目录,仅供参考:

@Async异步线程:Spring 自带的异步解决方案

2.1 启用@Async

        关键注解 @EnableAsync !!!可以加载启动类上,也可以加在配置文件上,效果是一样的。

  •  方式一:基于Springboot启动类启用
@EnableAsync
@SpringBootApplication
public class AsyncApplication {

    public static void main(String[] args) {
        SpringApplication.run(AsyncApplication.class, args);
    }

}
  • 方式二:基于Java配置的启用
// com.example.async.service 为即将开启异步线程业务的包位置(后面有详细讲解)
@EnableAsync
@Configuration
@ComponentScan("com.example.async.service")
public class AsyncConfiguration implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {
        return executor();
    }
    ...
}

2.2 @Async与线程池

        Spring应用默认的线程池,指在@Async注解在使用时,不指定线程池的名称。查看源码,@Async的默认线程池为SimpleAsyncTaskExecutor

@Slf4j
@Service
public class BusinessServiceImpl implements BusinessService {

    /**
     * 方法4:没有指定线程池,验证默认线程池也ok(不推荐:规避资源耗尽的风险)
     */
    @Async
    public void asyncDemo4() {
        log.info("asyncDemo4:" + Thread.currentThread().getName() + " 正在执行 ----------");
        try {
            Thread.sleep(2*1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("asyncDemo4:" + Thread.currentThread().getName() + " 执行结束!!");
    }
}

2.3 @Async自定义线程池

        自定义线程池,可对系统中线程池更加细粒度的控制,方便调整线程池大小配置,线程执行异常控制和处理。在设置系统自定义线程池代替默认线程池时,虽可通过多种模式设置,但替换默认线程池最终产生的线程池有且只能设置一个(不能设置多个类继承AsyncConfigurer)。

        自定义线程池有如下模式:

  1. 重新实现接口AsyncConfigurer;
  2. 继承AsyncConfigurerSupport;
  3. 配置由自定义的TaskExecutor替代内置的任务执行器;

        三者使用方式大体相同,下面的案例将展示说明其一:实现接口AsyncConfigurer接口的方式。

  • 配置一个线程池 ThreadPoolTaskExecutor
/**
 * com.example.async.service:即将开启异步线程的业务方法是哪个
 *
 * 解释:
 *  1.即将开启异步线程业务的包位置:com.example.async.service
 *  2.通过 ThreadPoolExecutor 的方式,规避资源耗尽的风险
 */
@EnableAsync
@Configuration
@ComponentScan("com.example.async.service")
public class AsyncConfiguration implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {
        return executor();
    }

    /**
     * 执行需要依赖线程池,这里就来配置一个线程池
     * 1.当池子大小小于corePoolSize,就新建线程,并处理请求
     * 2.当池子大小等于corePoolSize,把请求放入workQueue(QueueCapacity)中,池子里的空闲线程就去workQueue中取任务并处理
     * 3.当workQueue放不下任务时,就新建线程入池,并处理请求,如果池子大小撑到了maximumPoolSize,就用RejectedExecutionHandler来做拒绝处理
     * 4.当池子的线程数大于corePoolSize时,多余的线程会等待keepAliveTime长时间,如果无请求可处理就自行销毁
     */
    @Bean("asyncExecutor")
    public ThreadPoolTaskExecutor executor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //设置线程名
        executor.setThreadNamePrefix("async-method-execute-");
        //设置核心线程数
        executor.setCorePoolSize(10);
        //设置最大线程数
        executor.setMaxPoolSize(50);
        //线程池所使用的缓冲队列
        executor.setQueueCapacity(100);
        //设置多余线程等待的时间,单位:秒
        executor.setKeepAliveSeconds(10);
        // 初始化线程
        executor.initialize();
        return executor;
    }
}
  • 执行异步线程方法,指定线程池:value 要与配置类 Bean() 中的name相同 
/**
 * 异步线程 - 执行业务
 * 注意:
 *  1.@Async 注解调用用线程池,不指定的话默认:SimpleAsyncTaskExecutor
 *  2.SimpleAsyncTaskExecutor 不是真的线程池,这个类不重用线程,默认每次调用都会创建一个新的线程
 */
@Slf4j
@Service
public class AsyncServiceImpl implements AsyncService {
    /**
     * 方法1:@Async 标注为异步任务:执行此方法的时候,会单独开启线程来执行,不影响主线程的执行
     */
    @Async("asyncExecutor")
    public void asyncDemo1() {
        log.info("asyncDemo1:" + Thread.currentThread().getName() + " 正在执行 ----------");
        // 故意等10秒,那么异步线程开起来,这样明显看到:方法2不用等方法1执行完就调用了
        try {
            Thread.sleep(10*1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("asyncDemo1:" + Thread.currentThread().getName() + " 执行结束!!");
    }

    /**
     * 方法2:与方法1一起执行,证明2个线程异步执行,互不干扰
     */
    @Async("asyncExecutor")
    public void asyncDemo2() {
        log.info("asyncDemo2:" + Thread.currentThread().getName() + " 正在执行 ----------");
        try {
            Thread.sleep(5*1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("asyncDemo2:" + Thread.currentThread().getName() + " 执行结束!!");
    }

    /**
     * 方法3:没有指定线程池,验证默认线程池也ok(不推荐:规避资源耗尽的风险)
     */
    @Async
    public void asyncDemo3() {
        log.info("asyncDemo3:" + Thread.currentThread().getName() + " 正在执行 ----------");
        try {
            Thread.sleep(1*1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("asyncDemo3:" + Thread.currentThread().getName() + " 执行结束!!");
    }
}

2.4 启动测试

        通过 AsyncApplication 启动 SpringBoot 项目,Postman 进行接口测试:

http://127.0.0.1:8080/async/demo

  • 我写了4个demo,分别模拟4种情况,详情在注释里有写。
@Slf4j
@RestController
@RequestMapping("/async")
public class AsyncControllor {

    @Autowired
    private AsyncService asyncMethodService;
    @Autowired
    private BusinessService businessService;

    @GetMapping("/demo")
    public String demo()  {
        log.info("接口调用:【开始】 --------------------");
        try {
            // 执行异步任务 - 自定义线程池
            asyncMethodService.asyncDemo1();
            asyncMethodService.asyncDemo2();
            asyncMethodService.asyncDemo3();
            // 执行异步任务 - 默认线程池
            businessService.asyncDemo4();
        } catch (Exception e) {
            return "Exception";
        }
        log.info("接口调用:【结束】 --------------------");
        return "success";
    }
}
  • 运行结果:接口执行结束,异步线程仍在运行

@Async异步线程:Spring 自带的异步解决方案文章来源地址https://www.toymoban.com/news/detail-423536.html


总结

  1. @EnableAsync 是启动 @Async 异步线程的关键,可以加载启动类上,也可以加在配置文件上;
  2. 为了规避资源耗尽的风险,推荐通过 ThreadPoolExecutor 的方式创建线程池;
  3. @Async 注解标注在方法上,以便异步地调用该方法;

到了这里,关于@Async异步线程:Spring 自带的异步解决方案的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【前端面经】JS-异步解决方案

    同步和异步 众所周知, JavaScript 是一门单线程的语言, 单线程就意味着同一时间只能执行一个任务, 当前任务执行结束, 才会执行下一个任务. 这种模式的好处就是执行环境比较单纯, 但坏处也很明显, 一旦有某个任务卡住了, 就会导致整个程序阻塞. 为了解决这个问题, JS将任务的

    2024年02月02日
    浏览(33)
  • JS for循环异步解决方案

    JavaScript中的for循环是一种同步操作,它将阻塞代码的执行,直到循环完成。但是,在处理异步操作时,使用同步for循环会导致问题。 为了解决这个问题,可以使用以下两种异步解决方案: 递归是一种有效的解决方案,它可以确保异步操作按照预期执行。以下是使用递归实现

    2024年02月13日
    浏览(30)
  • Go异步任务解决方案 Asynq

    今天为大家介绍一个Go处理异步任务的解决方案:Asynq,是一个 Go 库,用于排队任务并与 worker 异步处理它们。它由Redis提供支持,旨在实现可扩展且易于上手。 Asynq 是一个 Go 库,用于对任务进行排队并与工作人员异步处理它们。 Asynq 工作原理的高级概述: 客户端将任务放入

    2024年01月17日
    浏览(32)
  • 笔记本外接键盘解决方案:禁用笔记本自带键盘

    笔记本外接键盘时,有时会将外接键盘放置在笔记本自带键盘上,加上现代笔记本设计轻薄,外接键盘(尤其是108键的)在使用过程中经常触碰自带键盘,禁用自带键盘后将提供极大的工作便利。 通过系统自带sc命令禁用PS/2(i8042prt)服务: 禁用服务: 恢复服务: I8042prt是

    2024年02月12日
    浏览(48)
  • 详解JS的四种异步解决方案!

    目录 同步异步的概念 js中异步的应用场景 实现异步的四种方法 1、 回调函数 2、Promise 3、Generator 4、 async/await         「异步编程」 是前端工程师日常开发中经常会用到的技术,也是校招面试过程中常考的一个知识点。         通过掌握 「异步编程」 的四种方式,可

    2024年01月18日
    浏览(34)
  • 【go】异步任务解决方案Asynq实战

    Asynq 是一个 Go 库,一个高效的分布式任务队列。 Asynq 工作原理: 客户端(生产者)将任务放入队列 服务器(消费者)从队列中拉出任务并为每个任务启动一个工作 goroutine 多个工作人员同时处理任务 git库:https://github.com/hibiken/asynq Asynq 使用 Redis 作为消息代理。client 和 se

    2024年02月10日
    浏览(32)
  • JavaScript 异步解决方案 Promise 全解析(转载)

    Promise 是一个 JS 的异步编程解决方案,解决了传统异步编程回调地狱的问题。 从语义上来说: Promise 是一个向外部传达异步编程操作消息的对象。 JS里一个promise可以有以下几种基本状态: nothing happened yet \\\"locked in\\\" to another promise fulfilled rejected 其中{1,2}为 pending ,{3,4}为 settl

    2024年02月08日
    浏览(52)
  • 【多线程】| 线程冲突解决方案

    同一进程内的线程是共享同一内存空间的,所以在多个线程的进程里,线程是可以同时操作这个进程空间的数据的,这样就容易造成线程冲突的情况。 举个小李子:一个房子里(代表一个进程),只有一个厕所(代表一个资源)。屋子里面有两个人A和B(代表两个线程),共

    2024年02月05日
    浏览(34)
  • 实现不同局域网文件共享的解决方案:使用Python自带HTTP服务和端口映射

    数据共享作为和连接作为互联网的基础应用,不仅在商业和办公场景有广泛的应用,对于个人用户也有很强的实用意义。也正因如此,大量数据共享软件被开发出来,云存储的概念也被重复炒作。对于爱好折腾的笔者来说,用最简单的工具找寻私人共享和存储解决方案,也是

    2024年02月11日
    浏览(34)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包