springboot @Async 注解如何实现方法异步

 更新时间:2021年11月20日 17:10:42   作者:Bug布道师  
这篇文章主要介绍了springboot @Async 注解如何实现方法异步,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

@Async注解如何实现方法异步

处理大批量数据的时候,效率很慢。所以考虑一下使用多线程。

刚开始自己手写的一套,用了线程池启动固定的线程数进行跑批。但是后来老大考虑到自己手写的风险不好控制,所以使用spring的方法。

这里没有详细介绍,只有简单的demo,只会用,不懂原理:

一、springboot的App类需要的注解

package com.xxx.xxx.xxx;
import java.util.concurrent.ThreadPoolExecutor;
import org.springframework.boot.web.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
/**
 * 类功能说明:服务生产者启动类
 * <p>
 * <strong></strong>
 * </p>
 *
 * @version
 * @author
 * @since 1.8
 */
@Configuration
@EnableAsync
public class Application extends SpringBootServletInitializer { 
    @Bean
    public TaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 设置核心线程数
        executor.setCorePoolSize(5);
        // 设置最大线程数
        executor.setMaxPoolSize(60);
        // 设置队列容量
        executor.setQueueCapacity(20);
        // 设置线程活跃时间(秒)
        executor.setKeepAliveSeconds(60);
        // 设置默认线程名称
        executor.setThreadNamePrefix("what-");
        // 设置拒绝策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 等待所有任务结束后再关闭线程池
        executor.setWaitForTasksToCompleteOnShutdown(true);        
        return executor;
    }    
}

springboot的App类,很简单,就能使用很多东西。

二、service层的注解

package com.xxx.xxx.service.impl; 
import java.util.concurrent.Future; 
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service; 
import com.xxx.xxx.service.XXXAsyncService ;
 
@Service
public class XXXAsyncServiceImpl implements XXXAsyncService { 
    @Async
    public Future<Long> rtn1() throws Exception {
        //do something
        //有返回值的时候,可以返回string,long之类的。
        return new AsyncResult<>(1);
    } 
    @Async
    public void rtn2() throws Exception {
        //do something
        //这个可以没有返回值.
    }
}

三、调用层

package com.xxx.xxx.controller; 
import java.util.concurrent.Future; 
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController; 
import com.xxx.xxx.service.XXXAsyncService;
 
@RestController
@RequestMapping(value="/xxx") 
public class XXXAsyncController { 
 @Autowired
 private XXXAsyncService xxxAsyncService; 
 /**
  * 这里调用异步方法
  */
    @RequestMapping(value = "/xxx")
 public void dodo() throws Exception {     
  int threads = 10;//十个线程
        List<Future<Long>> list = new ArrayList<>();
        for(int i = 0;i < threads; i++){
         //这里循环调用异步方法。
      //如果存在大量数据,可以在这里把数据切片,然后循环调用,分批处理数据。效率杠杠的。
   list .add(xxxAsyncService.rtn1());
        }
        long count = 0;
        for(Future<Long> l : tsfCountList) {
         //异步调用需要返回值的时候,这里可以把返回值都放入到list集合中,然后可以统一处理。 这里的get()是阻塞的,因为需要所以异步方法返回,在继续执行。
         count += l.get();
        }
        System.out.println("调用次数:" + count);
 }
}

这些代码全是手写,记录下来,以后用的时候,省的忘了,查起来麻烦。。

异步注解@Async的使用以及注意事项

第一步开启异步

@Configuration
@EnableAsync
public class SpringAsyncConfig { ... }

默认情况下,@EnableAsync检测Spring的@Async注释和EJB 3.1 javax. EJB .异步;此选项还可用于检测其他用户定义的注释类型。(也可以在SpringBoot的启动类上直接加@EnableAsync注解)

在 Spring 中,用 @Async 注解指定的方法,该方法被调用时会以异步的方式执行。而如果没有在 @Async 注解中指定线程池,就会使用默认的线程池。默认的线程池为 SimpleAsyncTaskExecutor 。

该线程池不会复用线程,每有一个新任务被提交,该线程池就会创建一个新的线程实例用于执行任务。下面为相关的代码:

protected void doExecute(Runnable task) {
    Thread thread = (this.threadFactory != null ? this.threadFactory.newThread(task) : createThread(task));
    thread.start();
}

而如果想要指定线程池,可以通过在 @Async 注解中的 value 参数中指定所要使用的线程池的 Bean Name 。另一种方法是是一个实现了 AsyncConfigurer 接口或是继承其默认适配器类 AsyncConfigurerSupport 的配置类,这样 @Async 注解的方法就会使用指定的自定义的线程池。

使用@Async注解的话采用的是springBoot默认的线程池,不过一般我们会自定义线程池(因为比较灵活),配置方式有:

  • 使用 xml 文件配置的方式
  • 使用Java代码结合@Configuration进行配置(推荐使用)

下面显示配置线程的代码实现

package com.deppon.ptos.load.config; 
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; 
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
 
/**
 * @Description: 异步线程管理
 * @Author:   LYH
 * @CreateDate: 2019/6/27 8:54
 * @Version: 1.0
 * @JDK: 1.8
 */
@Configuration
@EnableAsync
@Slf4j
public class ExecutorConfig {  
    @Value("${async.executor.thread.core_pool_size}")
    private int corePoolSize;
    @Value("${async.executor.thread.max_pool_size}")
    private int maxPoolSize;
    @Value("${async.executor.thread.queue_capacity}")
    private int queueCapacity;
    @Value("${async.executor.thread.name.prefix}")
    private String namePrefix;
 
    @Bean(name = "asyncServiceExecutor")
    public Executor asyncServiceExecutor() {
        log.info("start asyncServiceExecutor");
        ThreadPoolTaskExecutor executor = new VisiableThreadPoolTaskExecutor();
        //配置核心线程数
        executor.setCorePoolSize(corePoolSize);
        //配置最大线程数
        executor.setMaxPoolSize(maxPoolSize);
        //配置队列大小
        executor.setQueueCapacity(queueCapacity);
        //配置线程池中的线程的名称前缀
        executor.setThreadNamePrefix(namePrefix);
 
        // rejection-policy:当pool已经达到max size的时候,如何处理新任务
        // CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //执行初始化
        executor.initialize();
        return executor;
    }
}
package com.deppon.ptos.load.config;  
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.util.concurrent.ListenableFuture; 
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
 
/**
 * @Description: 打印异步线程的执行情况   使用Callbale Future 来返回线程的信息
 * @Author: 633805  LYH
 * @CreateDate: 2019/6/27 8:59
 * @Version: 1.0
 * @JDK: 1.8
 */
@Component
@Slf4j
public class VisiableThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {  
    private void showThreadPoolInfo(String prefix) {
        ThreadPoolExecutor threadPoolExecutor = getThreadPoolExecutor(); 
        if (null == threadPoolExecutor) {
            return;
        }
 
        log.info("{}, {},taskCount [{}], completedTaskCount [{}], activeCount [{}], queueSize [{}]",
                this.getThreadNamePrefix(),
                prefix,
                threadPoolExecutor.getTaskCount(),
                threadPoolExecutor.getCompletedTaskCount(),
                threadPoolExecutor.getActiveCount(),
                threadPoolExecutor.getQueue().size());
    }
 
    @Override
    public void execute(Runnable task) {
        showThreadPoolInfo("1. do execute");
        super.execute(task);
    }
 
    @Override
    public void execute(Runnable task, long startTimeout) {
        showThreadPoolInfo("2. do execute");
        super.execute(task, startTimeout);
    }
 
    @Override
    public Future<?> submit(Runnable task) {
        showThreadPoolInfo("1. do submit");
        return super.submit(task);
    }
 
    @Override
    public <T> Future<T> submit(Callable<T> task) {
        showThreadPoolInfo("2. do submit");
        return super.submit(task);
    }
 
    @Override
    public ListenableFuture<?> submitListenable(Runnable task) {
        showThreadPoolInfo("1. do submitListenable");
        return super.submitListenable(task);
    }
 
    @Override
    public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
        showThreadPoolInfo("2. do submitListenable");
        return super.submitListenable(task);
    }
}

使用:

@Async("asyncServiceExecutor")

到这一步,异步就算开启了。

下面主要说一说错误的

使用@Async导致异步不成功的情况

如下方式会使@Async失效

  • 异步方法使用static修饰
  • 异步类没有使用@Component注解(或其他注解)导致spring无法扫描到异步类
  • 异步方法不能与被调用的异步方法在同一个类中
  • 类中需要使用@Autowired或@Resource等注解自动注入,不能自己手动new对象
  • 如果使用SpringBoot框架必须在启动类中增加@EnableAsync注解

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Java 通过 二三法 巧解前端数据显示

    Java 通过 二三法 巧解前端数据显示

    实践来源于理论,做开发前肯定要先了解相关的规则和原理,看到标题或许你会好奇什么是二三法。本篇文章带你深入了解,需要的朋友可以参考下
    2021-10-10
  • Java 协程 Quasar详解

    Java 协程 Quasar详解

    这篇文章主要介绍了Java 协程 Quasar详解,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的小伙伴可以参考一下
    2022-07-07
  • springboot的控制反转和自动装配示例代码

    springboot的控制反转和自动装配示例代码

    这篇文章主要介绍了springboot的控制反转和自动装配的相关知识,本文通过示例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-06-06
  • SpringCloud-Alibaba-Nacos启动失败解决方案

    SpringCloud-Alibaba-Nacos启动失败解决方案

    这篇文章主要介绍了SpringCloud-Alibaba-Nacos启动失败解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-04-04
  • mybatis 一对一、一对多和多对多查询实例代码

    mybatis 一对一、一对多和多对多查询实例代码

    这篇文章主要介绍了mybatis 一对一、一对多和多对多查询的实例代码,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下
    2018-06-06
  • SpringBoot请求参数加密、响应参数解密的实现

    SpringBoot请求参数加密、响应参数解密的实现

    在项目开发工程中,有的项目可能对参数安全要求比较高,在整个http数据传输的过程中都需要对请求参数、响应参数进行加密,本文主要介绍了SpringBoot请求参数加密、响应参数解密的实现,感兴趣的可以了解一下
    2024-01-01
  • 使用Java8实现观察者模式的方法(上)

    使用Java8实现观察者模式的方法(上)

    本文给大家介绍使用java8实现观察者模式的方法,涉及到java8观察者模式相关知识,对此感兴趣的朋友一起学习吧
    2016-02-02
  • 解决poi导出时单元格样式被覆盖问题

    解决poi导出时单元格样式被覆盖问题

    这篇文章主要介绍了解决poi导出时单元格样式被覆盖问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-11-11
  • Springboot使用ResponseBody汉字返回问号问题

    Springboot使用ResponseBody汉字返回问号问题

    这篇文章主要介绍了Springboot使用ResponseBody汉字返回问号问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-06-06
  • Spring接口ApplicationRunner用法详解

    Spring接口ApplicationRunner用法详解

    这篇文章主要介绍了Spring接口ApplicationRunner的作用和使用介绍,本文通过示例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-08-08

最新评论