@Async

简介

使用spring快速开启异步执行服务的注解

应用场景

同步:同步就是整个处理过程顺序执行,当各个过程都执行完毕,并返回结果。

异步: 异步调用则是只是发送了调用的指令,调用者无需等待被调用的方法完全执行完毕;而是继续执行下面的流程。例如, 在某个调用中,需要顺序调用 A, B, C三个过程方法;如他们都是同步调用,则需要将他们都顺序执行完毕之后,方算作过程执行完毕; 如B为一个异步的调用方法,则在执行完A之后,调用B,并不等待B完成,而是执行开始调用C,待C执行完毕之后,就意味着这个过程执行完毕了。在Java中,一般在处理类似的场景之时,都是基于创建独立的线程去完成相应的异步调用逻辑,通过主线程和不同的业务子线程之间的执行流程,从而在启动独立的线程之后,主线程继续执行而不会产生停滞等待的情况。

使用

必要条件

​ spring环境

简单使用

1.在启动类或配置类加上@EnableAsync
@SpringBootApplication
@EnableAsync
public class AsyncDemoApplication { public static void main(String[] args) {
SpringApplication.run(AsyncDemoApplication.class, args);
} }
2.在服务类加上@Async注解
public interface TestService {

    void test();

    String mainTest();
}
@Service
public class TestServiceImpl implements TestService { @Override
@Async
public void test() {
try {
System.out.println("线程开始");
TimeUnit.SECONDS.sleep(10);
int a=1/0;
System.out.println("service test:"+Thread.currentThread().getName());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
} @Override
public String mainTest() {
System.out.println("service msinTest:"+Thread.currentThread().getName());
return "Hello World";
}
}
3.测试

注意:测试时普通方法和异步方法并行,如果串行(在一个方法中调用另一个方法)则不会有效果

@SpringBootTest
@RunWith(SpringRunner.class)
class AsyncDemoApplicationTests { @Resource
private TestService testService;
@Test
void contextLoads() {
String s = testService.mainTest();
if (s.equals("Hello World")){
testService.test();
}
System.out.println(s);
Scanner scanner = new Scanner(System.in);
int i = scanner.nextInt();
}
}
4.结果
service msinTest:main
Hello World
线程开始
2023-01-04 10:34:45.165 ERROR 22804 --- [ task-1] .a.i.SimpleAsyncUncaughtExceptionHandler : Unexpected exception occurred invoking async method: public void com.wangfan.service.impl.TestServiceImpl.test()
5.问题分析
  • 默认线程池的弊端

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

  • newFixedThreadPool和newSingleThreadExecutor:主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。

  • newCachedThreadPool和newScheduledThreadPool:要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。

    @Async默认异步配置使用的是SimpleAsyncTaskExecutor,该线程池默认来一个任务创建一个线程,若系统中不断的创建线程,最终会导致系统占用内存过高,引发OutOfMemoryError错误。针对线程创建问题,SimpleAsyncTaskExecutor提供了限流机制,通过concurrencyLimit属性来控制开关,当concurrencyLimit>=0时开启限流机制,默认关闭限流机制即concurrencyLimit=-1,当关闭情况下,会不断创建新的线程来处理任务。基于默认配置,SimpleAsyncTaskExecutor并不是严格意义的线程池,达不到线程复用的功能。

自定义线程池使用

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

开启和测试代码参考简单使用

1.自定义线程池
@Configuration
public class AsyncConfig { public static final String EXECUTOR_NAME = "testExecutor";
@Value("${thread-pool.config.corePoolSize:1}")
private int corePoolSize;
@Value("${thread-pool.config.maxPoolSize:2}")
private int maxPoolSize;
@Value("${thread-pool.config.queueCapacity:2}")
private int queueCapacity;
@Value("${thread-pool.config.threadNamePrefix:vchr-}")
private String threadNamePrefix; @Bean(name = EXECUTOR_NAME)
public Executor executor() {
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
// 核心线程数
threadPoolTaskExecutor.setCorePoolSize(corePoolSize);
// 最大线程数
threadPoolTaskExecutor.setMaxPoolSize(maxPoolSize);
// 阻塞队列容量
threadPoolTaskExecutor.setQueueCapacity(queueCapacity);
// 待任务在关机时完成--表明等待所有线程执行完
threadPoolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
// 线程名称前缀
threadPoolTaskExecutor.setThreadNamePrefix(threadNamePrefix);
// 设置拒绝策略,目前是直接拒绝
/**
* 到线程池的任务缓存队列已满并且线程池中的线程数到达了maxPoolSize,如果还有任务到来就会采取任务拒绝策略,
* 通常有以下四种策略:
* ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常
* ThreadPoolExecutor.DiscardPolicy:丢弃任务,但不抛出异常
* ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前边的任务,然后重新尝试执行任务(重复此过程)
* TreadPoolExecutor.CallerRunsPolicy:重试添加当前的任务,自动重复调用execute()方法,直到成功
*/
//初始化
threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
threadPoolTaskExecutor.initialize();
return threadPoolTaskExecutor;
}
}

2.指定服务类@Async的线程池

@Service
public class TestServiceImpl implements TestService { @Override
@Async(value="AsyncConfig.EXECUTOR_NAME")
public void test() {
try {
System.out.println("线程开始");
TimeUnit.SECONDS.sleep(10);
int a=1/0;
System.out.println("service test:"+Thread.currentThread().getName());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
} @Override
public String mainTest() {
System.out.println("service msinTest:"+Thread.currentThread().getName());
return "Hello World";
}
}

3.测试结果

service msinTest:main
Hello World
线程开始
2023-01-04 10:45:51.770 ERROR 25304 --- [ vchr-1] .a.i.SimpleAsyncUncaughtExceptionHandler : Unexpected exception occurred invoking async method: public void com.wangfan.service.impl.TestServiceImpl.test()

线程名称已改变

2.重新实现接口AsyncConfigurer
@Configuration
@Slf4j
public class AsyncConfig implements AsyncConfigurer { public static final String EXECUTOR_NAME = "testExecutor";
@Value("${thread-pool.config.corePoolSize:1}")
private int corePoolSize;
@Value("${thread-pool.config.maxPoolSize:2}")
private int maxPoolSize;
@Value("${thread-pool.config.queueCapacity:2}")
private int queueCapacity;
@Value("${thread-pool.config.threadNamePrefix:vchr-}")
private String threadNamePrefix; @Bean(name = EXECUTOR_NAME)
public Executor executor() {
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
// 核心线程数
threadPoolTaskExecutor.setCorePoolSize(corePoolSize);
// 最大线程数
threadPoolTaskExecutor.setMaxPoolSize(maxPoolSize);
// 阻塞队列容量
threadPoolTaskExecutor.setQueueCapacity(queueCapacity);
// 待任务在关机时完成--表明等待所有线程执行完
threadPoolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
// 线程名称前缀
threadPoolTaskExecutor.setThreadNamePrefix(threadNamePrefix);
// 设置拒绝策略,目前是直接拒绝
/**
* 到线程池的任务缓存队列已满并且线程池中的线程数到达了maxPoolSize,如果还有任务到来就会采取任务拒绝策略,
* 通常有以下四种策略:
* ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常
* ThreadPoolExecutor.DiscardPolicy:丢弃任务,但不抛出异常
* ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前边的任务,然后重新尝试执行任务(重复此过程)
* TreadPoolExecutor.CallerRunsPolicy:重试添加当前的任务,自动重复调用execute()方法,直到成功
*/
//初始化
threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
threadPoolTaskExecutor.initialize();
return threadPoolTaskExecutor;
} @Override
public Executor getAsyncExecutor() {
return executor();
} @Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (throwable, method, objects) -> log.error(throwable.getMessage());
}
}

2.指定服务类@Async的线程池可写可不写默认为自定义的(多个线城池需指定)

@Async
public void test() {
...
}

3.测试

service msinTest:main
Hello World
线程开始
2023-01-04 10:54:12.682 ERROR 27312 --- [ vchr-1] com.wangfan.config.AsyncConfig : / by zero

线程名称已改变

3.继承AsyncConfigurerSupport

@Configuration
@Slf4j
//@EnableAsync
public class AsyncConfig extends AsyncConfigurerSupport { public static final String EXECUTOR_NAME = "testExecutor";
@Value("${thread-pool.config.corePoolSize:1}")
private int corePoolSize;
@Value("${thread-pool.config.maxPoolSize:2}")
private int maxPoolSize;
@Value("${thread-pool.config.queueCapacity:2}")
private int queueCapacity;
@Value("${thread-pool.config.threadNamePrefix:vchr-}")
private String threadNamePrefix; @Bean(name = EXECUTOR_NAME)
public Executor executor() {
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
// 核心线程数
threadPoolTaskExecutor.setCorePoolSize(corePoolSize);
// 最大线程数
threadPoolTaskExecutor.setMaxPoolSize(maxPoolSize);
// 阻塞队列容量
threadPoolTaskExecutor.setQueueCapacity(queueCapacity);
// 待任务在关机时完成--表明等待所有线程执行完
threadPoolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
// 线程名称前缀
threadPoolTaskExecutor.setThreadNamePrefix(threadNamePrefix);
// 设置拒绝策略,目前是直接拒绝
/**
* 到线程池的任务缓存队列已满并且线程池中的线程数到达了maxPoolSize,如果还有任务到来就会采取任务拒绝策略,
* 通常有以下四种策略:
* ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常
* ThreadPoolExecutor.DiscardPolicy:丢弃任务,但不抛出异常
* ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前边的任务,然后重新尝试执行任务(重复此过程)
* TreadPoolExecutor.CallerRunsPolicy:重试添加当前的任务,自动重复调用execute()方法,直到成功
*/
//初始化
threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
threadPoolTaskExecutor.initialize();
return threadPoolTaskExecutor;
} @Override
public Executor getAsyncExecutor() {
return executor();
} @Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (throwable, method, objects) -> log.error(throwable.getMessage());
}
}

2.指定服务类@Async的线程池可写可不写默认为自定义的(多个线城池需指定)

@Async
public void test() {
...
}

3.测试

service msinTest:main
Hello World
线程开始
2023-01-04 10:54:12.682 ERROR 27312 --- [ vchr-1] com.wangfan.config.AsyncConfig : / by zero

线程名称已改变

@Async异步注解的使用的更多相关文章

  1. @Async异步注解与SpringBoot结合使用

    当你在service层需要启动异步线程去执行某些分支任务,又不希望显式使用Thread等线程相关类,只想专注于实现业务逻辑代码开发,可以使用@Async异步注解. 1. 使用@Async 异步注解 C ...

  2. spring boot使用@Async异步注解

    1.java的大部分接口的方法都是串行执行的,但是有些业务场景是不需要同步返回结果的,可以把结果直接返回,具体业务异步执行,也有些业务接口是需要并行获取数据,最后把数据聚合在统一返回给前端. 通常我们 ...

  3. async异步注解和aspect切面注解等注解的原理

    在我们使用spring框架的过程中,在很多时候我们会使用@async注解来异步执行某一些方法,提高系统的执行效率.今天我们来探讨下spring是如何完成这个功能的. 1.spring 在扫描bean的 ...

  4. spring boot @Async异步注解上下文透传

    上一篇文章说到,之前使用了@Async注解,子线程无法获取到上下文信息,导致流量无法打到灰度,然后改成 线程池的方式,每次调用异步调用的时候都手动透传 上下文(硬编码)解决了问题. 后面查阅了资料,找 ...

  5. 关于Dubbo和Spring异步注解@Async的冲突

    项目中难免会有异步处理的需求,像异步记录日志啦,异步发送邮件啦,而Dubbo又是现在主流的分布式框架,所有异步+Dubbo的组合是再所难免的 但博主是实践中发现Dubbo的服务并不能很好的跟Sprin ...

  6. Spring中异步注解@Async的使用、原理及使用时可能导致的问题

    前言 其实最近都在研究事务相关的内容,之所以写这么一篇文章是因为前面写了一篇关于循环依赖的文章: <面试必杀技,讲一讲Spring中的循环依赖> 然后,很多同学碰到了下面这个问题,添加了S ...

  7. springboot:使用异步注解@Async的那些坑

    springboot:使用异步注解@Async的那些坑 一.引言 在java后端开发中经常会碰到处理多个任务的情况,比如一个方法中要调用多个请求,然后把多个请求的结果合并后统一返回,一般情况下调用其他 ...

  8. springboot:嵌套使用异步注解@Async还会异步执行吗

    一.引言 在前边的文章<[springboot:使用异步注解@Async的那些坑>中介绍了使用@Async注解获取任务执行结果的错误用法,今天来分享下另外一种常见的错误. 二.代码演示 下 ...

  9. springboot:使用异步注解@Async的前世今生

    在前边的文章中,和小伙伴一起认识了异步执行的好处,以及如何进行异步开发,对,就是使用@Async注解,在使用异步注解@Async的过程中也存在一些坑,不过通过正确的打开方式也可以很好的避免,今天想和大 ...

  10. 异步编程系列第01章 Async异步编程简介

    p { display: block; margin: 3px 0 0 0; } --> 2016.10.11补充 三个月过去了,回头来看,我不得不承认这是一系列失败的翻译.过段时间,我将重新翻 ...

随机推荐

  1. 虚拟机中debian11修改控制台(console)分辨率|linux控制台分辨率|linux console resolution

    实体机一般安装好驱动分辨率就没啥问题,而且个人pc也没有只用控制台的需求.但是虚拟机中不安装桌面的时候,默认的控制台分辨率常不能满足需求. 这个需求貌似也比较少,而且几乎搜到的文章大部分都是旧的,不能 ...

  2. linux合并bilibili下载的blv视频

    B站下载的学习视频,用python+sh合并处理,自己mark一下 手机bilibili下载了视频,想要搞到电脑上看,结果发现下载下来的都是文件夹里的一堆片段,仔细一看还是 .blv,脑那样啊! 在虚 ...

  3. ucocIII野火

    5.1裸机系统 5.1.1 轮询系统 轮询系统即是在裸机编程的时候,先初始化好相关的硬件,然后让主程序在一个死循环里面不断循环,顺序地做各种事情.轮询系统是一种非常简单的软件结构,通常只适用于那些只需 ...

  4. Django的urls的配置

    在一个请求到达的时候,最先达到的就是视图层,然后根据url映射到视图函数.这一部分我们来说明url的配置. 概述 为了给一个应用设计URL,你需要创建一个Python 模块,通常称为URLconf(U ...

  5. Win10应用商店经常遭遇打不开的情况,如错误代码0x80131500

    1.用"win + R"打开运行 2.输入 inetcpl.cpl 打开Internet属性(或从IE浏览器设置打开) 3 点击高级选项 4 找到并勾选 TLS 1.2,取消勾选T ...

  6. ABAP 指定字符替换为空格

    上代码 DATA:str1 TYPE string VALUE '小红##爱#six##小绿#666'. *******DATA(str1) = '小红##爱#six##小绿#666'. " ...

  7. asp.net core 解决用户上传文件提示 System.UnauthorizedAccessException: Access to the path 'C:\Windows\TEMP\ASPNETCORE_e65c14f7-e337-493c-90ac-d49a48db7187.tmp' is denied.

    今天发布项目到服务器 上传文件突然提示 System.UnauthorizedAccessException: Access to the path 'C:\Windows\TEMP\ASPNETCO ...

  8. windows下rabbitmq启动报错--distribution port 25672 in use by another node: rabbit@DESKTOP-LLPGVVE

    最近公司有需求需要用到rabbitmq,因为之前习惯用的都是activemq,所以要临时学习一下,捣鼓这个rabbitmq.想着先在本地捣鼓测试一下,然后按照这个博主分享的安装方式进行安装. http ...

  9. pypi镜像-清华

    临时使用 pip install -i https://pypi.tuna.tsinghua.edu.cn/simple some-package 注意,simple 不能少, 是 https 而不是 ...

  10. 正确引用R及R包

    R版本不断更新,为保证数据可重复性,引用R时需标注出对应的R版本.那么如何引用呢? 打开R,键入citation(),得到对应的版本引用信息. To cite R in publications us ...