一、使用方式
1、启动类⾥⾯使⽤
@EnableAsync
注解开启功能,⾃动扫描
2、定义异步任务类并使⽤
@Component
标记组件被容器扫描
,
异步⽅法加上@Async
@Component
@Slf4j
public class SmsComponent {
/**
* 异步方法
*/
@Async
public void send() {
System.out.println("方法逻辑");
}
}
二、失效情况
1、注解
@Async
的⽅法不是
public
⽅法
2、注解
@Async
的返回值只能为
void
或者
Future
3、注解
@Async
⽅法使⽤
static
修饰也会失效
4、spring
⽆法扫描到异步类,没加注解
@Async
或 @EnableAsync注解
5、调⽤⽅与被调⽅不能在同⼀个类
Spring
在扫描
bean
的时候会扫描⽅法上是否包含
@Async 注解,动态地⽣成⼀个⼦类(即proxy
代理类),当这个有注解的⽅法被调⽤的时候,实际上是由代理类来调⽤的,代理类在调⽤时增加异步作⽤如果这个有注解的⽅法是被同⼀个类中的其他⽅法调⽤的,那么该⽅法的调⽤并没有通过代理类,⽽是直接通过原来的那个 bean
,所以就失效了所以调⽤⽅与被调⽅不能在同⼀个类,主要是使⽤了动态代理,同⼀个类的时候直接调⽤,不是通过⽣成的动态代理类调⽤⼀般将要异步执⾏的⽅法单独抽取成⼀个类类中需要使⽤
@Autowired
或
@Resource
等注解⾃动注⼊,不能⾃⼰⼿动new
对象
6、在
Async
⽅法上标注
@Transactional
是没⽤的,但在
Async ⽅法调⽤的⽅法上标注@Transactional
是有效的
三、注意事项
@Async
注解没指定线程池的话,即未设置TaskExecutor时默认使⽤Spring
创建ThreadPoolTaskExecutor,
默认
8
个核⼼线程数占⽤满了之后
,
新的调⽤就会进⼊队列
,
最
⼤值是Integer.MAX_VALUE,极容易出现
OOM
,或者消息丢失。
附加:
ThreadPoolTaskExecutor
和
ThreadPoolExecutor区别?
1、ThreadPoolExecutor
,这个类是
JDK
中的线程池类,继承⾃ Executor,⾥⾯有⼀个
execute()
⽅法,⽤来执⾏线程,线程 池主要提供⼀个线程队列,队列中保存着所有等 待状态的线 程,避免了创建与销毁的额外开销。
2、ThreadPoolTaskExecutor
,是
spring
包下的,是
Spring
为我 们提供的线程池类 Spring 异步线程池的接⼝类是
TaskExecutor
,本质还是 java.util.concurrent.Executor
源代码位置:
TaskExecutionProperties
public static class Pool {
/**
* Queue capacity. An unbounded capacity does not increase the pool and therefore
* ignores the "max-size" property.
*/
private int queueCapacity = Integer.MAX_VALUE;
/**
* Core number of threads.
*/
private int coreSize = 8;
/**
* Maximum allowed number of threads. If tasks are filling up the queue, the pool
* can expand up to that size to accommodate the load. Ignored if the queue is
* unbounded.
*/
private int maxSize = Integer.MAX_VALUE;
/**
* Whether core threads are allowed to time out. This enables dynamic growing and
* shrinking of the pool.
*/
private boolean allowCoreThreadTimeout = true;
TaskExecutionAutoConfiguration
四、解决方案
spring
会先搜索
TaskExecutor
类型的
bean
或者名字为 taskExecutor的
Executor
类型的
bean,
所以我们最好来⾃定义⼀个线程池,加⼊
Spring IOC
容器⾥⾯,即可覆盖
@Configuration
@EnableAsync
public class ThreadPoolTaskConfig {
@Bean("threadPoolTaskExecutor")
public ThreadPoolTaskExecutor threadPoolTaskExecutor(){
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//线程池创建的核心线程数,线程池维护线程的最少数量,即使没有任务需要执行,也会一直存活
//如果设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭
executor.setCorePoolSize(4);
//executor.setAllowCoreThreadTimeOut();
//阻塞队列 当核心线程数达到最大时,新任务会放在队列中排队等待执行
executor.setQueueCapacity(10000);
//最大线程池数量,当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务
//任务队列已满时, 且当线程数=maxPoolSize,,线程池会拒绝处理任务而抛出异常
executor.setMaxPoolSize(8);
//当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize
//允许线程空闲时间30秒,当maxPoolSize的线程在空闲时间到达的时候销毁
//如果allowCoreThreadTimeout=true,则会直到线程数量=0
executor.setKeepAliveSeconds(30);
//spring 提供的 ThreadPoolTaskExecutor 线程池,是有setThreadNamePrefix() 方法的。
//jdk 提供的ThreadPoolExecutor 线程池是没有 setThreadNamePrefix() 方法的
executor.setThreadNamePrefix("xcw-");
// rejection-policy:当pool已经达到max size的时候,如何处理新任务
// CallerRunsPolicy():交由调用方线程运行,比如 main 线程;如果添加到线程池失败,那么主线程会自己去执行该任务,不会等待线程池中的线程去执行
// AbortPolicy():该策略是线程池的默认策略,如果线程池队列满了丢掉这个任务并且抛出RejectedExecutionException异常。
// DiscardPolicy():如果线程池队列满了,会直接丢掉这个任务并且不会有任何异常
// DiscardOldestPolicy():丢弃队列中最老的任务,队列满了,会将最早进入队列的任务删掉腾出空间,再尝试加入队列
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
//
使⽤实战, 启动类可以不加
@EnableAsync
,改上⾯加
@Async("threadPoolTaskExecutor")