-
2021-09-13 18:26:43
@Async失效
1、异步方法使用static修饰
2、异步类没有使用@Component注解(或其他注解)导致spring无法扫描到异步类
3、异步方法不能与异步方法在同一个类中
4、类中需要使用@Autowired或@Resource等注解自动注入,不能自己手动new对象
5、如果使用SpringBoot框架必须在启动类中增加@EnableAsync注解
6、在Async 方法上标注@Transactional是没用的。 在Async 方法调用的方法上标注@Transactional 有效。
7、调用被@Async标记的方法的调用者不能和被调用的方法在同一类中不然不会起作用。
8、使用@Async时要求是不能有返回值的不然会报错的 因为异步要求是不关心结果的。9、@Async会让spring解决循环依赖的方法失效,在类上添加@EnableAsync注解
更多相关内容 -
带有@Transactional和@Async的循环依赖问题
2020-12-21 02:06:20今天我们来探讨一个有意思的spring源码问题,也是一个学生告诉了我现象我从源码里面找到了这个有意思的问题。 首先我们看service层的代码案例,如下: @Service(... @Async @Override public void transa -
带有@Transactional和@Async的循环依赖问题的解决
2020-08-19 06:08:26主要介绍了带有@Transactional和@Async的循环依赖问题的解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧 -
springboot中@Async默认线程池导致OOM问题
2020-08-19 03:16:34主要介绍了springboot中@Async默认线程池导致OOM问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧 -
Spring Boot异步调用@Async过程详解
2020-08-25 10:29:54主要介绍了Spring Boot异步调用@Async过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 -
Spring boot注解@Async线程池实例详解
2020-08-25 06:43:22主要介绍了Spring boot注解@Async线程池实例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 -
Spring中@Async注解执行异步任务的方法
2020-08-27 09:27:29在业务处理中,有些业务使用异步的方式更为合理,这篇文章主要介绍了Spring中@Async注解执行异步任务的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧 -
Spring Boot利用@Async如何实现异步调用:自定义线程池
2020-08-27 14:49:59主要给大家介绍了关于Spring Boot利用@Async如何实现异步调用:自定义线程池的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 -
Spring Boot利用@Async异步调用:使用Future及定义超时详解
2020-08-27 14:51:18主要给大家介绍了关于Spring Boot利用@Async异步调用:使用Future及定义超时的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用spring boot具有一定的参考学习价值,需要的朋友可以参考下 -
spiring异步方法@Async和消息队列rocketMQ使用哪个比较好?
2021-01-07 12:05:27背景: 因为项目是分布式项目,已经集成过rocketmq.但是没有单独将消息服务提取出来.而是每个model之间相互进行消息发布与消费. 这样解耦性比较差.且仍需要model与model之间进行服务的调用.....所以考虑将消息服务提取... -
spring boot中使用@Async实现异步调用任务
2020-08-31 07:18:32本篇文章主要介绍了spring boot中使用@Async实现异步调用任务,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧 -
浅谈Spring @Async异步线程池用法总结
2020-08-29 21:27:39本篇文章主要介绍了浅谈Spring @Async异步线程池用法总结,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 -
Spring @async方法如何添加注解实现异步调用
2020-08-25 04:05:30主要介绍了Spring @async方法如何添加注解实现异步调用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 -
关于Spring注解@Async引发其他注解失效的解决
2020-08-27 19:21:06主要介绍了关于Spring注解@Async引发其他注解失效的解决,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧 -
Spring Boot利用@Async异步调用:ThreadPoolTaskScheduler线程池的优雅关闭详解
2020-08-27 14:52:28主要给大家介绍了关于Spring Boot利用@Async异步调用:ThreadPoolTaskScheduler线程池的优雅关闭的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考借鉴,下面随着小编来一起学习学习吧 -
深入理解spring boot异步调用方式@Async
2020-08-30 00:11:42Spring为任务调度与异步方法执行提供了注解支持。通过在方法上设置@Async注解,可使得方法被异步调用。下面这篇文章主要给大家介绍了关于spring boot异步调用方式@Async的相关资料,需要的朋友可以参考下。 -
Spring中@Async用法详解及简单实例
2020-08-31 11:05:25主要介绍了Spring中@Async用法详解及简单实例的相关资料,需要的朋友可以参考下 -
JAVA 中Spring的@Async用法总结
2020-08-31 00:58:58主要介绍了JAVA 中Spring的@Async用法总结的相关资料,需要的朋友可以参考下 -
SpringBoot项目@Async方法问题解决方案
2020-08-19 07:48:09主要介绍了SpringBoot项目@Async方法问题解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 -
Spring boot线程池、@Async、@Async控制个数、@Async线程池一直增加
2021-04-13 19:08:06Spring boot线程池、@Async、@Async控制个数、@Async线程池一直增加 import org.springframework.context.annotation.Bean; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; ...Spring boot线程池、@Async、@Async控制个数、@Async线程池一直增加
import org.springframework.context.annotation.Bean; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import java.util.concurrent.Executor; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Scheduled; @Slf4j @Component public class UdpQueueServer{ 设置连接池信息 @Bean("taskExecutor") public Executor taskExecutor() { ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler(); executor.setPoolSize(100); //最大个数 executor.setThreadNamePrefix("test-"); //线程池名称 //该方法就是这里的关键,用来设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean,这样这些异步任务的销毁就会先于Redis线程池的销毁 executor.setWaitForTasksToCompleteOnShutdown(true); //该方法用来设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住。 executor.setAwaitTerminationSeconds(10); return executor; } @Async("taskExecutor") //与上方@Bean中值相同 @Scheduled(fixedDelay = 3000) //3秒启动一个线程 public void consumerMessage() { boolean isRunning = true; while (isRunning) { try { Thread.sleep(1000); log.info("test"); } catch (Exception e) { e.printStackTrace(); log.info("{}",e); } } } }
-
@Async注解的使用 及@Async 注解失效问题的分析与解决方案
2019-12-09 22:51:21注解有时候也会失效的情况,@Transactional注解失效是因为 它是基于AOP的,而AOP又是基于动态代理实现的,@Async注解也是一样的,在我们后面的例子中,真正调用 doAsync01 和 doAsync02 方法的是 AsyncService 的...在开发过程中,我们会遇到很多使用线程池的业务场景,例如异步短信通知、异步记录操作日志。大多数使用线程池的场景,就是会将一些可以进行异步操作的业务放在线程池中去完成。
例如在生成订单的时候给用户发送短信,生成订单的结果不应该被发送短信的成功与否所左右,也就是说生成订单这个主操作是不依赖于发送短信这个操作,所以我们就可以把发送短信这个操作置为异步操作。
那么本文就是来看看Spring中提供的优雅的异步处理方案:在Spring3中,Spring中引入了一个新的注解@Async,这个注解让我们在使用Spring完成异步操作变得非常方便。作配置,使用@Async即可实现。
@Async的使用
但让我们来实现一个小的demo:
注意在启动类要加上 @EnableAsync 注解;
@RestController @Slf4j public class AsyncTestController { @GetMapping("test") public void testAsync() throws InterruptedException { log.info("=====主线程执行: " + Thread.currentThread().getName()); this.doAsync01(); this.doAsync02(); log.info("=====主线程执行: " + Thread.currentThread().getName()); } @Async public void doAsync01() throws InterruptedException { Thread.sleep(3000); log.info("=====子线程执行: " + Thread.currentThread().getName()); } @Async public void doAsync02() { log.info("=====子线程执行: " + Thread.currentThread().getName()); } }
执行结果如下:
从线程名和执行时间可以看出,@Async注解并没有起到预期的作用,只不过是串行的单行线程罢了。
发现问题:@Async注解不起作用
但是,当我们把异步方法放到另一个类中:
@RestController @Slf4j public class AsyncTestController { @Autowired private AsyncService asyncService; @GetMapping("test") public void testAsync() throws InterruptedException { log.info("=====主线程执行: " + Thread.currentThread().getName()); asyncService.doAsync01(); asyncService.doAsync02(); log.info("=====主线程执行: " + Thread.currentThread().getName()); } }
@Service @Slf4j public class AsyncService { @Async("defaultTaskExecutor") public void doAsync01() throws InterruptedException { Thread.sleep(3000); log.info("=====子线程1执行: " + Thread.currentThread().getName()); } @Async public void doAsync02() { log.info("=====子线程2执行: " + Thread.currentThread().getName()); } }
执行结果如下:
有次可见 @Async注解是有用的,上面只是我们使用的方式不正确。但是这是为什么呢?
问题分析
这个问题与我们以前遇到的一个问题十分的相似,不知道大家以前可遇到过@Transactional 注解有时候也会失效的情况,@Transactional注解失效是因为 它是基于AOP的,而AOP又是基于动态代理实现的,@Async注解也是一样的,在我们后面的例子中,真正调用 doAsync01 和 doAsync02 方法的是 AsyncService 的代理对象。其实Spring容器在初始化的时候Spring容器会将含有AOP注解的类对象“替换”为代理对象(简单这么理解),那么注解失效的原因就很明显了,就是因为调用方法的是对象本身而不是代理对象,因为没有经过Spring容器,那么解决方法也会沿着这个思路来解决。
解决方案
我们上面第二个例子的解决方法就是将要异步执行的方法单独抽取成一个类,这样的确可以解决异步注解失效的问题,原理就是当把执行异步的方法单独抽取成一个类的时候,这个类肯定是被Spring管理的,其他Spring组件需要调用的时候肯定会注入进去,这时候实际上注入进去的就是代理类了,其实还有其他的解决方法,并不一定非要单独抽取成一个类。
其他的解决方案:
- 在AsyncTestController 中通过上下文获取自己的代理对象调用异步方法
其实我们的注入对象都是从Spring容器中给当前Spring组件进行成员变量的赋值,由于TestService使用了AOP注解,那么实际上TestService在Spring容器中实际存在的是它的代理对象。
代码实现如下:
@RestController @Slf4j public class AsyncTestController { @GetMapping("test") public void testAsync() throws InterruptedException { log.info("=====主线程执行: " + Thread.currentThread().getName()); AsyncTestController asyncTestController = SpringUtils.getBean(AsyncTestController.class); asyncTestController.doAsync01(); asyncTestController.doAsync02(); log.info("=====主线程执行: " + Thread.currentThread().getName()); } @Async("defaultTaskExecutor") public void doAsync01() throws InterruptedException { Thread.sleep(3000); log.info("=====子线程1执行: " + Thread.currentThread().getName()); } @Async("defaultTaskExecutor") public void doAsync02() { log.info("=====子线程2执行: " + Thread.currentThread().getName()); } }
SpringUtils工具类:
@Component("springContextUtil") public class SpringUtils implements ApplicationContextAware { private static ApplicationContext applicationContext = null; public static ApplicationContext getApplicationContext() { return applicationContext; } @SuppressWarnings("unchecked") public static <T> T getBean(String beanId) { return (T) applicationContext.getBean(beanId); } public static <T> T getBean(Class<T> requiredType) { return (T) applicationContext.getBean(requiredType); } /** * Spring容器启动后,会把 applicationContext 给自动注入进来,然后我们把 applicationContext * 赋值到静态变量中,方便后续拿到容器对象 * * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) */ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { SpringUtils.applicationContext = applicationContext; } }
执行结果如下:成功
- 开启cglib代理手动获取spring代理类:
在启动类加上@EnableAspectJAutoProxy(exposeProxy = true) 注解,
使用AopContext.currentProxy()获取当前代理类:
这里为了证明Spring容器中的对象就是当前代理类对象特地输出了一句话:
@RestController @Slf4j public class AsyncTestController { @GetMapping("test") public void testAsync() throws InterruptedException { log.info("=====主线程执行: " + Thread.currentThread().getName()); AsyncTestController currentProxy = (AsyncTestController) AopContext.currentProxy(); currentProxy.doAsync01(); currentProxy.doAsync02(); log.info("=====主线程执行: " + Thread.currentThread().getName()); } @Async("defaultTaskExecutor") public void doAsync01() throws InterruptedException { Thread.sleep(3000); log.info("=====子线程1执行: " + Thread.currentThread().getName()); } @Async("defaultTaskExecutor") public void doAsync02() { log.info("=====子线程2执行: " + Thread.currentThread().getName()); } }
执行结果如下:
并没有像上面的方法一样成功,反而报错了 ,但是@EnableAspectJAutoProxy(exposeProxy = true)注解命名就加了呀,为啥还会报 java.lang.IllegalStateException: Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.呢? 详见:https://blog.csdn.net/weixin_40910372/article/details/103565970。
@Async注解失败的常见场景
好了,我们回到现在的问题上来,从网上找了很多关于会导致@Async注解失败的原因,这里总结一下,希望能帮到大家。
- 没有在@SpringBootApplication启动类当中添加注解@EnableAsync注解。
- 异步方法使用注解@Async的返回值只能为void或者Future。
- 没有走Spring的代理类。
其中的后两种是我们比较常见的失误了。
解决方法如下:1.注解的方法必须是public方法。
2.方法一定要从另一个类中调用,也就是从类的外部调用,类的内部调用是无效的。
3.如果需要从类的内部调用,需要先获取其代理类,通过代理类调用异步方法。@Async注解使用细节
-
@Async注解一般用在方法上,如果用在类上,那么这个类所有的方法都是异步执行的;
-
@Async可以放在任何方法上,哪怕你是private的(若是同类调用,请务必注意注解失效的情况~~~)
-
所使用的@Async注解方法的类对象应该是Spring容器管理的bean对象
-
@Async可以放在接口处(或者接口方法上)。但是只有使用的是JDK的动态代理时才有效,CGLIB会失效。因此建议:统一写在实现类的方法上
-
需要注解@EnableAsync来开启异步注解的支持
-
若你希望得到异步调用的返回值,请你的返回值用Futrue变量包装起来
- 在AsyncTestController 中通过上下文获取自己的代理对象调用异步方法
-
Spring中@Async注解实现异步调详解
2020-08-19 07:50:33在本篇文章里小编给大家分享的是关于Spring中@Async注解实现异步调详解内容,需要的朋友们可以学习下。 -
SpringBoot利用@Async注解实现异步调用
2022-01-03 21:23:32@Configuration //主要是为了扫描范围包下的所有 @Async注解 @EnableAsync public class AsyncConfiguration { } 3.2、在方法上标记异步调用 增加一个Component类,用来进行业务处理,同时添加@Async注解,代表该...前言:异步编程是让程序并发运行的一种手段,使用异步编程可以大大提高我们程序的吞吐量,减少用户的等待时间。在Java并发编程中实现异步功能,一般是需要使用
线程
或者线程池
。而实现一个线程,要么继承Thread
类,要么实现Runnable
接口,然后在run方法中写具体的业务逻辑代码。开发Spring的大神们,为了简化这类异步操作,已经帮我们把异步功能封装好了。Spring中提供了@Async
注解,我们可以通过它即可开启异步功能,使用起来非常方便。一、异步编程
异步编程允许多个事情同时发生,当程序调用需要长时间运行的方法时,它不会阻塞当前的执行流程,程序可以继续运行,当方法执行完成时通知给主线程根据需要获取其执行结果或者失败异常的原因。使用异步编程可以大大提高我们程序的吞吐量,可以更好的面对更高的并发场景并更好的利用现有的系统资源,同时也会一定程度上减少用户的等待时间等。
1.1、什么是异步调用?
异步调用是相对于同步调用而言的,同步调用是指程序按预定顺序一步步执行,每一步必须等到上一步执行完后才能执行,异步调用则无需等待上一步程序执行完即可执行。异步调用可以减少程序执行时间。
1.2、如何实现异步调用?
多线程,这是很多人第一眼想到的关键词,没错,多线程就是一种实现异步调用的方式。
-
在非spring目项目中我们要实现异步调用的就是使用多线程方式,可以自己实现Runable接口或者集成Thread类,或者使用jdk1.5以上提供了的Executors线程池;
-
从Spring3开始提供了@Async注解,用于标注某个方法或某个类里面的所有方法都是需要异步处理的。被注解的方法被调用的时候,会在新线程中执行,而调用它的方法会在原来的线程中执行。这样可以避免阻塞、以及保证任务的实时性。适用于处理log、发送邮件、短信……等。
二、Java 实现异步编程的几种方式
2.1、非Spring项目利用多线程
①直接new线程
在 Java 语言中最简单使用异步编程的方式就是创建一个 Thread 来实现,如果你使用的 JDK 版本是 8 以上的话,可以使用 Lambda 表达式会更加简洁。
Thread t = new Thread() { @Override public void run() { longTimeMethod(); } }; t.start();
但是new Thread()只能作为示例使用,如果用到了生产环境发生事故后果自负,使用上面这种 Thread 方式异步编程存在两个明显的问题。
-
创建线程没有复用。我们知道频繁的线程创建与销毁是需要一部分开销的,而且示例里也没有限制线程的个数,如果使用不当可能会把系统线程用尽,从而引发事故,这个问题使用线程池可以解决。
-
异步任务无法获取最终的执行结果,可以使用FutureTask 的方式。
②使用线程池
private ExecutorService executor = Executors.newCachedThreadPool() ; public void fun() throws Exception { executor.submit(new Runnable(){ @override public void run() { try { //要执行的业务代码,我们这里没有写方法,可以让线程休息几秒进行测试 Thread.sleep(10000); System.out.print("睡够啦~"); }catch(Exception e) { throw new RuntimeException("报错啦!!"); } } }); }
Executors是concurrent包下的一个类,为我们提供了创建线程池的简便方法。
Executors可以创建我们常用的四种线程池:
(1)newCachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。不设上限,提交的任务将立即执行。
(2)newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
(3)newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
(4)newSingleThreadExecutor 创建一个单线程化的线程池执行任务。
submit方法
线程池建立完毕之后,我们就需要往线程池提交任务。通过线程池的submit方法即可,submit方法接收两种Runable和Callable。
区别如下:
Runable是实现该接口的run方法,callable是实现接口的call方法。
callable允许使用返回值,callable允许抛出异常。
2.2、Spring项目异步任务处理,@Async的配置和使用
从Spring3开始提供了@Async注解用于异步方法调用,该注解可以被标注在方法上,以便异步地调用该方法。调用者将在调用时立即返回,方法的实际执行将提交给Spring TaskExecutor的任务中,由指定的线程池中的线程执行。
-
@Async应用默认线程池,指在@Async注解在使用时,不指定线程池的名称。查看源码,@Async的默认线程池为SimpleAsyncTaskExecutor。
-
@Async默认异步配置使用的是SimpleAsyncTaskExecutor,该线程池默认来一个任务创建一个线程,若系统中不断的创建线程,最终会导致系统占用内存过高,引发OutOfMemoryError错误。基于默认配置,SimpleAsyncTaskExecutor并不是严格意义的线程池,达不到线程复用的功能。
-
在项目应用中,@Async调用线程池,推荐使用自定义线程池的模式。自定义线程池常用方案:重新实现接口AsyncConfigurer。
三、SpringBoot利用@Async注解实现异步调用
使用@Async注解开启的异步功能,默认情况下,每次都会创建一个新线程。如果在高并发的场景下,可能会产生大量的线程,从而导致OOM问题。所以,大家在使用@Async注解的异步功能时,请别忘了自定义一个
线程池。
3.1、新建配置类,开启@Async功能支持
使用@EnableAsync来开启异步任务支持,@EnableAsync注解可以直接放在SpringBoot启动类上,也可以单独放在其他配置类上。我们这里选择使用单独的配置类SyncConfiguration。
@Configuration //主要是为了扫描范围包下的所有 @Async注解 @EnableAsync public class AsyncConfiguration { }
3.2、在方法上标记异步调用
增加一个Component类,用来进行业务处理,同时添加@Async注解,代表该方法为异步处理;如果标注在类上,则类里面的所有方法都是需要异步处理的。
@Component @Slf4j public class AsyncTask { @SneakyThrows @Async public void doTask1() { long t1 = System.currentTimeMillis(); Thread.sleep(2000); long t2 = System.currentTimeMillis(); log.info("task1 cost {} ms" , t2-t1); } @SneakyThrows @Async public void doTask2() { long t1 = System.currentTimeMillis(); Thread.sleep(3000); long t2 = System.currentTimeMillis(); log.info("task2 cost {} ms" , t2-t1); } }
3.3、在Controller中进行异步方法调用
@RestController @RequestMapping("/async") @Slf4j public class AsyncController { @Autowired private AsyncTask asyncTask; @RequestMapping("/task") public void task() throws InterruptedException { long t1 = System.currentTimeMillis(); asyncTask.doTask1(); asyncTask.doTask2(); Thread.sleep(1000); long t2 = System.currentTimeMillis(); log.info("main cost {} ms", t2-t1); } }
通过访问http://localhost:8080/async/task查看控制台日志:主线程不需要等待异步方法执行完成,减少了响应时间,提高了接口性能。
2021-11-25 15:48:37 [http-nio-8080-exec-8] INFO AsyncController:26 - main cost 1009 ms 2021-11-25 15:48:38 [task-1] INFO com.async.AsyncTask:22 - task1 cost 2005 ms 2021-11-25 15:48:39 [task-2] INFO com.async.AsyncTask:31 - task2 cost 3005 ms
通过上面三步我们就可以在SpringBoot中欢乐的使用异步方法来提高我们接口性能了,是不是很简单?不过,如果你在实际项目开发中真这样写了,肯定会被老鸟们无情嘲讽?因为上面的代码忽略了一个最大的问题,就是给@Async异步框架自定义线程池 。
四、@Async自定义线程池
4.1、为什么要给@Async自定义线程池?
使用
@Async
注解,在默认情况下用的是SimpleAsyncTaskExecutor线程池,该线程池不是真正意义上的线程池 。使用此线程池无法实现线程重用,每次调用都会新建一条线程。若系统中不断的创建线程,最终会导致系统占用内存过高,引发OutOfMemoryError
错误。关键代码如下:
public void execute(Runnable task, long startTimeout) { Assert.notNull(task, "Runnable must not be null"); Runnable taskToUse = this.taskDecorator != null ? this.taskDecorator.decorate(task) : task; //判断是否开启限流,默认为否 if (this.isThrottleActive() && startTimeout > 0L) { //执行前置操作,进行限流 this.concurrencyThrottle.beforeAccess(); this.doExecute(new SimpleAsyncTaskExecutor.ConcurrencyThrottlingRunnable(taskToUse)); } else { //未限流的情况,执行线程任务 this.doExecute(taskToUse); } } protected void doExecute(Runnable task) { //不断创建线程 Thread thread = this.threadFactory != null ? this.threadFactory.newThread(task) : this.createThread(task); thread.start(); } //创建线程 public Thread createThread(Runnable runnable) { //指定线程名,task-1,task-2... Thread thread = new Thread(this.getThreadGroup(), runnable, this.nextThreadName()); thread.setPriority(this.getThreadPriority()); thread.setDaemon(this.isDaemon()); return thread; }
我们也可以直接通过上面的控制台日志观察,每次打印的线程名都是[task-1]、[task-2]、[task-3]、[task-4].....递增的。所以我们在使用Spring中的@Async异步框架时一定要自定义线程池,替代默认的
SimpleAsyncTaskExecutor
。Spring提供了多种线程池:
-
SimpleAsyncTaskExecutor
:不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程。 -
SyncTaskExecutor
:这个类没有实现异步调用,只是一个同步操作。只适用于不需要多线程的地方。 -
ConcurrentTaskExecutor
:Executor的适配类,不推荐使用。如果ThreadPoolTaskExecutor不满足要求时,才用考虑使用这个类 -
ThreadPoolTaskScheduler
:可以使用cron表达式 -
ThreadPoolTaskExecutor
:最常使用,推荐。其实质是对java.util.concurrent.ThreadPoolExecutor的包装
4.2、为@Async实现一个自定义线程池
@Configuration @EnableAsync public class SyncConfiguration { @Bean(name = "asyncPoolTaskExecutor") public ThreadPoolTaskExecutor executor() { ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); //核心线程数 taskExecutor.setCorePoolSize(10); //线程池维护线程的最大数量,只有在缓冲队列满了之后才会申请超过核心线程数的线程 taskExecutor.setMaxPoolSize(100); //缓存队列 taskExecutor.setQueueCapacity(50); //许的空闲时间,当超过了核心线程出之外的线程在空闲时间到达之后会被销毁 taskExecutor.setKeepAliveSeconds(200); //异步方法内部线程名称 taskExecutor.setThreadNamePrefix("async-"); /** * 当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略 * 通常有以下四种策略: * ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 * ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 * ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程) * ThreadPoolExecutor.CallerRunsPolicy:重试添加当前的任务,自动重复调用 execute() 方法,直到成功 */ taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); taskExecutor.initialize(); return taskExecutor; } }
配置自定义线程池以后我们就可以大胆的使用
@Async
提供的异步处理能力了。4.3、@Async配置默认线程池和多个线程池处理
在现实的互联网项目开发中,针对高并发的请求,一般的做法是高并发接口单独线程池隔离处理。
假设现在2个高并发接口:一个是修改用户信息接口,刷新用户redis缓存;一个是下订单接口,发送app push信息。往往会根据接口特征定义两个线程池,这时候我们在使用
@Async
时就需要通过指定线程池名称进行区分。(1)为@Async指定线程池名字
@SneakyThrows @Async("asyncPoolTaskExecutor") public void doTask1() { long t1 = System.currentTimeMillis(); Thread.sleep(2000); long t2 = System.currentTimeMillis(); log.info("task1 cos }
当系统存在多个线程池时,我们也可以配置一个默认线程池,对于非默认的异步任务再通过
@Async("otherTaskExecutor")
来指定线程池名称。(2)配置默认线程池
可以修改配置类让其实现
AsyncConfigurer
,并重写getAsyncExecutor()
方法,指定默认线程池:@Configuration @EnableAsync @Slf4j public class AsyncConfiguration implements AsyncConfigurer { @Bean(name = "asyncPoolTaskExecutor") public ThreadPoolTaskExecutor executor() { ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); //核心线程数 taskExecutor.setCorePoolSize(2); //线程池维护线程的最大数量,只有在缓冲队列满了之后才会申请超过核心线程数的线程 taskExecutor.setMaxPoolSize(10); //缓存队列 taskExecutor.setQueueCapacity(50); //许的空闲时间,当超过了核心线程出之外的线程在空闲时间到达之后会被销毁 taskExecutor.setKeepAliveSeconds(200); //异步方法内部线程名称 taskExecutor.setThreadNamePrefix("async-"); /** * 当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略 * 通常有以下四种策略: * ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 * ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 * ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程) * ThreadPoolExecutor.CallerRunsPolicy:重试添加当前的任务,自动重复调用 execute() 方法,直到成功 */ taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); taskExecutor.initialize(); return taskExecutor; } /** * 指定默认线程池 */ @Override public Executor getAsyncExecutor() { return executor(); } @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return (ex, method, params) -> log.error("线程池执行任务发送未知错误,执行方法:{}",method.getName(),ex); } }
如下,
doTask1()
方法使用默认使用线程池asyncPoolTaskExecutor
,doTask2()
使用线程池otherTaskExecutor
,非常灵活。@Async public void doTask1() { long t1 = System.currentTimeMillis(); Thread.sleep(2000); long t2 = System.currentTimeMillis(); log.info("task1 cost {} ms" , t2-t1); } @SneakyThrows @Async("otherTaskExecutor") public void doTask2() { long t1 = System.currentTimeMillis(); Thread.sleep(3000); long t2 = System.currentTimeMillis(); log.info("task2 cost {} ms" , t2-t1); }
@Async
异步方法在日常开发中经常会用到,很有必要掌握。
参考链接:
-
-
【spring】解决因@Async引起的循环依赖报错
2022-04-09 17:37:10最近在项目中使用@Async注解在方法上引起了循环依赖报错。 代码类似如下: package com.morris.spring.entity.circular; import org.springframework.beans.factory.annotation.Autowired; import org.spring... -
springboot使用@Async,和@Async整合线程池
2020-07-05 18:01:47@Async使用,最好把线程放到单独的一个类中。 代码实现 用户登录异步写入登录日志。 1. 登录伪代码 /** 异步执行类 */ @Autowired private AsyncLoginLogManage asyncLoginLogManage; @Override public ... -
深度解析@Async引起的循环依赖
2021-11-17 15:18:24什么是@Async @Async注解是Spring为我们提供的异步调用的注解,@Async可以作用到类或者方法上,标记了@Async注解的方法将会在独立的线程中被执行,调用者无需等待它的完成,即可继续其他的操作。从源码中可以看到... -
SpringBoot关于@Async线程池配置
2022-02-23 09:34:11SpringBoot关于@Async线程池配置 我们在Spring项目的时候,会用到异步注解 @Async 注解,从 Spring原理之@Async 我们可以知道其实他底层用到的默认的所谓的线程池并不是真的线程池,每次调用都会创建一个新的线程,... -
Spring中异步注解@Async的使用、原理及使用时可能导致的问题及解决方法
2020-08-18 18:38:45主要介绍了Spring中异步注解@Async的使用、原理及使用时可能导致的问题及解决方法,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下 -
【老王读Spring AOP-6】@Async产生AOP代理的原理
2021-11-11 09:28:40@Async产生AOP代理的原理前言版本约定正文AsyncAnnotationBeanPostProcessorAsyncAnnotationAdvisorAsyncAnnotationAdvisor 对应的 PointcutAsyncAnnotationAdvisor 对应的 Advice: ...