异步任务是相对同步调用而言,同步是程序按照预定顺序一步一步的执行,而异步不需要等上一步程序执行完就可以执行,多线程是实现异步调用的一种方式,MQ 宏观上的一种异步;
处理log,发送邮件、验证码,涉及到网络IO调用的操作
springboot中异步任务的使用很简单,只需要在启动类上加上@EnableAsync, 然后在需要异步的方法里面加上 @Async;一般需要江宁异步任务单独写在一个类里面,不能异步方法和调用这个异步方法在同一个类里面;异步任务所在地类需要加上component 纳入IOC容器;
1、异步方法不是public的;
2、异步方法返回值只能是void或者future;
3、异步方法使用static修饰会失效;
4、spring无法扫描到异步类,即异步方法所在类没有加@component,
5、调用方与被调用方在同一个类里面也会失效,即异步方法调用方与被调动方不能再同一个类里面
spring在扫描bean的时候会扫描方法行是否有@Async注解,动态的生成一个子类即proxy代理类,当这个注解的方法被调用的时候,实际上是由代理类来调用,代理类在调用的时候增加异步作用。
如果这个注解的方法是被同一个类中的其他方法调用的,那么该方法的调用并没有通过代理类,而是直接通过原来哪个bean,所以会失效;
一般要将异步方法单独抽成一个类,类中需要使用@autowired 或者@Resource等注解自动注入,不能手动new一个对象;
在Async方法上加 @transactional 是没用的,但是在调用async方法的方法上加事务是有效的;
容易出现OMM内存溢出
直接使用@Async 注解没有指定线程池的话,默认使用spring创建的ThreadPoolTaskExecutor,核心线程池数是8,最大线程池数是IntegerMAX_VALUE(21亿),队列使用的是LinkedBlockingQueue;空闲线程保存60s;拒绝策略是AbortPolicy,当请求过多线程都被阻塞的时候就容易OMM;
解决方案:使用自定义线程池;
- @Configuration
- @EnableAsync
- public class ThreadPoolTaskConfig {
-
- @Bean("threadPoolTaskExecutor")
- public ThreadPoolTaskExecutor threadPoolTaskExecutor(){
-
- ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
-
- //线程池创建的核心线程数,线程池维护线程的最少数量,即使没有任务需要执行,也会一直存活
- //如果设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭
- executor.setCorePoolSize(16);
- //executor.setAllowCoreThreadTimeOut();
-
- //阻塞队列 当核心线程数达到最大时,新任务会放在队列中排队等待执行
- executor.setQueueCapacity(124);
-
- //最大线程池数量,当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务
- //任务队列已满时, 且当线程数=maxPoolSize,,线程池会拒绝处理任务而抛出异常
- executor.setMaxPoolSize(64);
-
- //当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize
- //允许线程空闲时间30秒,当maxPoolSize的线程在空闲时间到达的时候销毁
- //如果allowCoreThreadTimeout=true,则会直到线程数量=0
- executor.setKeepAliveSeconds(30);
-
- //spring 提供的 ThreadPoolTaskExecutor 线程池,是有setThreadNamePrefix() 方法的。
- //jdk 提供的ThreadPoolExecutor 线程池是没有 setThreadNamePrefix() 方法的
- executor.setThreadNamePrefix("自定义线程池-");
-
- // rejection-policy:当pool已经达到max size的时候,如何处理新任务
- // CallerRunsPolicy():交由调用方线程运行,比如 main 线程;如果添加到线程池失败,那么主线程会自己去执行该任务,不会等待线程池中的线程去执行
- // AbortPolicy():该策略是线程池的默认策略,如果线程池队列满了丢掉这个任务并且抛出RejectedExecutionException异常。
- // DiscardPolicy():如果线程池队列满了,会直接丢掉这个任务并且不会有任何异常
- // DiscardOldestPolicy():丢弃队列中最老的任务,队列满了,会将最早进入队列的任务删掉腾出空间,再尝试加入队列
-
- executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
-
- executor.initialize();
- return executor;
- }
- }
使用的时候 由原来的 @Async 改为 @Async("threadPoolTaskExecutor");
还存在的问题是阻塞队列中的线程还在内存,但是此时服务器宕机就会丢失;