Spring Boot中异步线程池@Async详解

Spring Boot中异步线程池@Async详解

很多业务场景需要使用异步去完成,比如:发送短信通知。要完成异步操作一般有两种:

1、消息队列MQ

2、线程池处理。

我们来看看Spring框架中如何去使用线程池来完成异步操作,以及分析背后的原理。

一. Spring异步线程池的接口类 :TaskExecutor

在Spring4中,Spring中引入了一个新的注解@Async,这个注解让我们在使用Spring完成异步操作变得非常方便。

Spring异步线程池的接口类,其实质是java.util.concurrent.Executor

Spring 已经实现的异常线程池:

1. SimpleAsyncTaskExecutor:不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程。

2. SyncTaskExecutor:这个类没有实现异步调用,只是一个同步操作。只适用于不需要多线程的地方

3. ConcurrentTaskExecutor:Executor的适配类,不推荐使用。如果ThreadPoolTaskExecutor不满足要求时,才用考虑使用这个类

4. SimpleThreadPoolTaskExecutor:是Quartz的SimpleThreadPool的类。线程池同时被quartz和非quartz使用,才需要使用此类

5. ThreadPoolTaskExecutor :最常使用,推荐。 其实质是对java.util.concurrent.ThreadPoolExecutor的包装,

二、简单使用说明

Spring中用@Async注解标记的方法,称为异步方法。在spring boot应用中使用@Async很简单:

1、调用异步方法类上或者启动类加上注解@EnableAsync

2、在需要被异步调用的方法外加上@Async

3、所使用的@Async注解方法的类对象应该是Spring容器管理的bean对象;

启动类加上注解@EnableAsync:

import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.scheduling.annotation.EnableAsync; @SpringBootApplication@EnableAsyncpublic class CollectorApplication { public static void main(String[] args) throws Exception {SpringApplication.run(CollectorApplication.class, args);}}

在需要被异步调用的方法外加上@Async,同时类AsyncService加上注解@Service或者@Component,使其对象成为Spring容器管理的bean对象;

import org.springframework.beans.factory.annotation.Autowired;import org.springframework.scheduling.annotation.Async;import org.springframework.stereotype.Component;import org.springframework.transaction.annotation.Transactional; @Service@Transactionalpublic class AsyncService { @Async public void asyncMethod(String s) { System.out.println(“receive:” + s); } public void test() { System.out.println(“test”); asyncMethod();//同一个类里面调用异步方法 } @Async public void test2() { AsyncService asyncService = context.getBean(AsyncService.class); asyncService.asyncMethod();//异步 } /** * 异布调用返回Future */ @Async public Future asyncInvokeReturnFuture(int i) { System.out.println(“asyncInvokeReturnFuture, parementer=”+ i); Future future; try { Thread.sleep(1000 * 1); future = new AsyncResult(“success:” + i); } catch (InterruptedException e) { future = new AsyncResult(“error”); } return future; }} //异步方法和普通的方法调用相同asyncService.asyncMethod(“123”);Future future = asyncService.asyncInvokeReturnFuture(100);System.out.println(future.get());

如果将一个类声明为异步类@Async,那么这个类对外暴露的方法全部成为异步方法。

@Async@Servicepublic class AsyncClass { public AsyncClass() { System.out.println(“—-init AsyncClass—-“); } volatile int index = 0; public void foo() { System.out.println(“asyncclass foo, index:” + index); } public void foo(int i) { this.index = i; System.out.println(“asyncclass foo, index:” + i); } public void bar(int i) { this.index = i; System.out.println(“asyncclass bar, index:” + i); }}

这里需要注意的是:

1、同一个类里面调用异步方法不生效:原因默认类内的方法调用不会被aop拦截,即调用方和被调用方是在同一个类中,是无法产生切面的,该对象没有被Spring容器管理。即@Async方法不生效。

解决办法:如果要使同一个类中的方法之间调用也被拦截,需要使用spring容器中的实例对象,而不是使用默认的this,因为通过bean实例的调用才会被spring的aop拦截

本例使用方法:AsyncService asyncService = context.getBean(AsyncService.class); 然后使用这个引用调用本地的方法即可达到被拦截的目的

备注:这种方法只能拦截protected,default,public方法,private方法无法拦截。这个是spring aop的一个机制。

2、如果不自定义异步方法的线程池默认使用SimpleAsyncTaskExecutor。SimpleAsyncTaskExecutor:不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程。并发大的时候会产生严重的性能问题。

3、异步方法返回类型只能有两种:void和java.util.concurrent.Future。

1)当返回类型为void的时候,方法调用过程产生的异常不会抛到调用者层面,

可以通过注AsyncUncaughtExceptionHandler来捕获此类异常

2)当返回类型为Future的时候,方法调用过程产生的异常会抛到调用者层面

三、定义通用线程池


1、定义线程池

在Spring Boot主类中定义一个线程池,public Executor taskExecutor() 方法用于自定义自己的线程池,线程池前缀”taskExecutor-”。如果不定义,则使用系统默认的线程池。

@SpringBootApplicationpublic class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } @EnableAsync @Configuration class TaskPoolConfig { @Bean public Executor taskExecutor1() { ThreadPoolTaskExecutor pool = new ThreadPoolTaskExecutor(); pool.setCorePoolSize(5); //线程池活跃的线程数 pool.setMaxPoolSize(10); //线程池最大活跃的线程数 pool.setWaitForTasksToCompleteOnShutdown(true); pool.setThreadNamePrefix(“defaultExecutor”); return pool; } @Bean(“taskExecutor”) public Executor taskExecutor2() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(10); executor.setMaxPoolSize(20); executor.setQueueCapacity(200); executor.setKeepAliveSeconds(60); executor.setThreadNamePrefix(“taskExecutor-“); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); executor.setWaitForTasksToCompleteOnShutdown(true); executor.setAwaitTerminationSeconds(60); return executor; } }}

上面我们通过ThreadPoolTaskExecutor创建了一个线程池,同时设置了如下参数:

核心线程数10:线程池创建时初始化的线程数

最大线程数20:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程

缓冲队列200:用来缓冲执行任务的队列

允许线程的空闲时间60秒:超过了核心线程数之外的线程,在空闲时间到达之后会被销毁

线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池

线程池对拒绝任务的处理策略:此处采用了CallerRunsPolicy策略,当线程池没有处理能力的时候,该策略会直接在execute方法的调用线程中运行被拒绝的任务;如果执行程序已被关闭,则会丢弃该任务

设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean

设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住

也可以单独类来配置线程池:

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; /** * Created by huangguisu on 2020/6/10. */ @Configuration@EnableAsyncpublic class MyThreadPoolConfig { private static final int CORE_POOL_SIZE = 10; private static final int MAX_POOL_SIZE = 20; private static final int QUEUE_CAPACITY = 200; public static final String BEAN_EXECUTOR = “bean_executor”; /** * 事件和情感接口线程池执行器配置 * @return 事件和情感接口线程池执行器bean * */ @Bean(BEAN_EXECUTOR) public Executor executor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(CORE_POOL_SIZE); executor.setMaxPoolSize(MAX_POOL_SIZE); // 设置队列容量 executor.setQueueCapacity(QUEUE_CAPACITY); // 设置线程活跃时间(秒) executor.setKeepAliveSeconds(60); executor.setThreadNamePrefix(“SE-Pool#Task”); // 设置拒绝策略 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); executor.initialize(); return executor; }}

2、异步方法使用线程池

只需要在@Async注解中指定线程池名即可

@Componentpublic class Task { //默认使用线程池 @Async public void doTaskOne() throws Exception { System.out.println(“开始做任务”); long start = System.currentTimeMillis(); Thread.sleep(random.nextInt(10000)); long end = System.currentTimeMillis(); System.out.println(“完成任务耗时:” + (end – start) + “毫秒”); } //根据Bean Name指定特定线程池 @Async(“taskExecutor”) public void doTaskOne() throws Exception { System.out.println(“开始做任务”); long start = System.currentTimeMillis(); Thread.sleep(random.nextInt(10000)); long end = System.currentTimeMillis(); System.out.println(“完成任务耗时:” + (end – start) + “毫秒”); }}

3、通过xml配置定义线程池

Bean文件配置: spring_async.xml1. 线程的前缀为xmlExecutor2. 启动异步线程池配置

启动类导入xml文件:

@SpringBootApplication@ImportResource(“classpath:/async/spring_async.xml”)public class AsyncApplicationWithXML { private static final Logger log = LoggerFactory.getLogger(AsyncApplicationWithXML.class); public static void main(String[] args) { log.info(“Start AsyncApplication.. “); SpringApplication.run(AsyncApplicationWithXML.class, args); } }

线程池参数说明

1. ‘id’ : 线程名称的前缀

2. ‘pool-size’:线程池的大小。支持范围”min-max”和固定值(此时线程池core和max sizes相同)

3. ‘queue-capacity’ :排队队列长度

4. ‘rejection-policy’: 对方拒绝的任务处理策略

5. ‘keep-alive’ : 线程保护时间(单位秒)

四、异常处理

上面也提到:在调用方法时,可能出现方法中抛出异常的情况。在异步中主要有有两种异常处理方法:

1. 对于方法返回值是Futrue的异步方法:

a) 、一种是在调用future的get时捕获异常;

b)、 在异常方法中直接捕获异常

2. 对于返回值是void的异步方法:通过AsyncUncaughtExceptionHandler处理异常

@Componentpublic class AsyncException { /** * 带参数的异步调用 异步方法可以传入参数 * 对于返回值是void,异常会被AsyncUncaughtExceptionHandler处理掉 * @param s */ @Async public void asyncInvokeWithException(String s) { log.info(“asyncInvokeWithParameter, parementer={}”, s); throw new IllegalArgumentException(s); } /** * 异常调用返回Future * 对于返回值是Future,不会被AsyncUncaughtExceptionHandler处理,需要我们在方法中捕获异常并处理 * 或者在调用方在调用Futrue.get时捕获异常进行处理 * * @param i * @return */ @Async public Future asyncInvokeReturnFuture(int i) { System.out.println(“asyncInvokeReturnFuture, parementer={}”, i); Future future; try { Thread.sleep(1000 * 1); future = new AsyncResult(“success:” + i); throw new IllegalArgumentException(“a”); } catch (InterruptedException e) { future = new AsyncResult(“error”); } catch(IllegalArgumentException e){ future = new AsyncResult(“error-IllegalArgumentException”); } return future; } }

实现AsyncConfigurer接口对异常线程池更加细粒度的控制a) 创建线程自己的线程池b) 对void方法抛出的异常处理的类AsyncUncaughtExceptionHandler

@Servicepublic class MyAsyncConfigurer implements AsyncConfigurer{ @Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor(); threadPool.setCorePoolSize(1); threadPool.setMaxPoolSize(1); threadPool.setWaitForTasksToCompleteOnShutdown(true); threadPool.setAwaitTerminationSeconds(60 * 15); threadPool.setThreadNamePrefix(“MyAsync-“); threadPool.initialize(); return threadPool; } @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return new MyAsyncExceptionHandler(); } /** * 自定义异常处理类 */ class MyAsyncExceptionHandler implements AsyncUncaughtExceptionHandler { @Override public void handleUncaughtException(Throwable throwable, Method method, Object… obj) { System.out.println(“Exception message – ” + throwable.getMessage()); System.out.println(“Method name – ” + method.getName()); for (Object param : obj) { System.out.println(“Parameter value – ” + param); } } } }

五、问题

上面也提到:如果不自定义异步方法的线程池默认使用SimpleAsyncTaskExecutor。SimpleAsyncTaskExecutor:不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程。并发大的时候会产生严重的性能问题。

一般的错误OOM:OutOfMemoryError:unable to create new native thread,创建线程数量太多,占用内存过大.

解决办法:一般最好使用自定义线程池,做一些特殊策略, 比如自定义拒绝策略,如果队列满了,则拒绝处理该任务。

原文链接:https://blog.csdn.net/hguisu/article/details/106671893

郑重声明:本文内容及图片均整理自互联网,不代表本站立场,版权归原作者所有,如有侵权请联系管理员(admin#wlmqw.com)删除。
(0)
用户投稿
上一篇 2022年6月24日
下一篇 2022年6月24日

相关推荐

  • 人这辈子,挣钱的目的是为了啥?

    人性商战,一针见血! 欲望 欲望就像是一团火一样。 你离的太远就会被冻死。 你离的太近就会被烧死。 那些离钱太远的人,最终都会穷死。 那些离钱太近的人,注定一辈子都会成为金钱的奴隶…

    2022年6月15日
  • 情感:逆境中走出的人,心智才会更加成熟,得到你想得到的

    勇气,是面对困难时我们应该采取的态度。 人生,就是一场战斗,能在这场战斗中获胜的永远都是那些有勇气的人。 他们敢于面对常人难以面对的困难,他们从不会向生活投降,他们以自己的智慧和勇…

    2022年9月14日
  • 农业银行提前还房贷违约金是多少 详细的介绍来了!

    对于很多贷款买房的人群来说,手上有多余的闲钱后,都会拿来提前还房贷,但违约金的问题要注意,那么农业银行提前还房贷违约金是多少?下文就来带大家了解一下。 农业银行的房贷要是进行提前还…

    2022年10月2日
  • 散文:素年锦时,时光无恙,在八月里收获静好安然

    七月的风,八月的雨,时光的暮夏已悄悄地走向我和你。 从炎热的七月中,就期待荷风穿过花海能带来无上的清凉。时光一路匆匆,终究是没有等来那场如约而至的夏风,来稀释暑热,就已步入崭新的八…

    2022年8月3日
  • 坦然接受生活给予的一切

    人生中,总有那么多的迷茫与无奈,也都会不可避免地伴随着这样那样的大遗憾和小遗憾,为错失与失误而遗憾,为始终希望拥有却终归不属于自己的东西而遗憾。小遗憾仅是一刻间的捶胸顿足,大遗憾却…

    2022年7月6日
  • 汽车量产路上,那些「涉险」的测试工程师们

    头图来源 | 视觉中国 这次事故,坚定了我买意外险的念头。 6月22日17时20分,在蔚来位于上海嘉定区的全球总部,一辆测试车从停车楼三楼冲出坠落,消息很快在大大小小的行业群里传播…

    2022年6月26日
  • 5号秀加兰德拿1.93亿,状元胖虎榜眼莫兰特也拿1.93亿!那探花呢

    看着同届状元榜眼拿到5年1.93亿的顶薪肥约,巴雷特心里满是羡慕嫉妒恨啊。想当初巴雷特高中时期曾力压锡安,成为全美第一高中生。但如今他俩,已经对其完成了弯道超车。 而且2019届不…

    2022年7月20日
  • 价格做到两千以下的“3K屏”笔记本:酷比魔方GTBook 13评测

    如果我告诉大家有这样一款笔记本电脑,屏幕分辨率为3K(3000*2000)、内存为12GB、存储空间为256GB、但价格则是1799,大家会怎么想,是不是跟我一样认为有些离谱了?而…

    2022年8月21日
  • 智美科普|分清4类印第安纹

    大家都知道苹果肌处的两条凹陷状纹路叫印第安纹,但印第安纹还分了很多种类型,不同类型在改善方式上也有所不同。 第一种:肥胖型 这种类型的印第安纹,通常是因为肥胖导致的。变胖之后,由于…

    2022年7月5日
  • 《原神》久战演试怎么完成?《原神》久战演试通关方法

    原神久战演试怎么做,关于久战演试在游戏中属于是新活动中的高风的演试的第三个阶段,按照其中的要求进行摧毁无处怪物的营地从而即可完成任务得到奖励的,不过建议大家带来风系列角色从而聚怪,…

    2022年10月9日

联系我们

联系邮箱:admin#wlmqw.com
工作时间:周一至周五,10:30-18:30,节假日休息