当前位置: 首页 > 后端技术 > Java

大家都说不建议直接使用@Async注解?为什么??

时间:2023-04-01 19:29:18 Java

来源:www.cnblogs.com/wlandwl/p/async.html本文介绍@Async注解及其在Spring系统中的应用。本文只讲解@Async注解的应用规则,暂不介绍其原理、调用逻辑和源码分析。对于异步方法的调用,从Spring3开始就提供了@Async注解,可以在方法上标注,异步调用该方法。调用者在调用时会立即返回,方法的实际执行会提交给SpringTaskExecutor的任务,由指定线程池中的线程执行。项目应用中@Async调用线程池,推荐使用自定义线程池方式。自定义线程池常用解决方案:重新实现接口AsyncConfigurer。简介应用场景同步:同步是整个处理过程的顺序执行,每个过程完成后返回结果。异步:异步调用只是发送调用指令,调用者不需要等待被调用方法执行完毕;相反,它继续执行以下过程。比如在某个调用中,需要依次调用A、B、C这三个流程方法;如果都是同步调用,需要在流程执行完成之前依次执行;如果B是一个异步的call方法,在A执行完之后,调用B,并不等待B执行完,而是开始调用C,C执行完之后,就意味着流程完成了。在Java中,在处理类似的场景时,一般都是基于创建一个独立的线程来完成相应的异步调用逻辑。通过主线程与不同业务子线程之间的执行过程,在启动独立线程后,主线程继续执行,不会停滞等待。Spring已经实现的线程池SimpleAsyncTaskExecutor:不是真正的线程池,该类不重用线程,默认每次调用都会创建一个新线程。SyncTaskExecutor:这个类没有实现异步调用,它只是一个同步操作。仅适用于不需要多线程的情况。ConcurrentTaskExecutor:Executor的适配类,不推荐。ThreadPoolTask??Executor不满足要求才考虑使用这个类。SimpleThreadPoolTask??Executor:Quartz的SimpleThreadPool类。仅当线程池同时被quartz和非quartz使用时才需要这个类。ThreadPoolTask??Executor:最常用,推荐。它的本质是java.util.concurrent.ThreadPoolExecutor的包装。异步方法有:最简单的异步调用,返回值为void参数的异步调用,异步方法可以传入参数和返回值,常规调用返回FutureSpring启用@Async//基于Java配置的启用方法:@Configuration@EnableAsyncpublicclassSpringAsyncConfig{...}//Springboot启用:@EnableAsync@EnableTransactionManagementpublicclassSettlementApplication{publicstaticvoidmain(String[]args){SpringApplication.run(SettlementApplication.class,args);}}@Async应用默认线程池Spring应用默认线程池,也就是说在使用@Async注解时,不指定线程池的名称。查看源码,@Async默认的线程池是SimpleAsyncTaskExecutor。我不会介绍SpringBoot的基础知识。推荐这个实用教程:https://github.com/javastacks...无返回值调用是基于@Async无返回值调用,直接使用类和使用方法(推荐使用方法),添加注释。如果需要抛出异常,需要手动new一个异常来抛出。/***异步调用带参数异步方法可以传入参数*对于返回值为void,异常将由AsyncUncaughtExceptionHandler处理*@params*/@AsyncpublicvoidasyncInvokeWithException(Strings){log.info("asyncInvokeWithParameter,参数={}",s);thrownewIllegalArgumentException(s);}有返回值Futurecall/***异常调用返回Future*对于返回值为Future,不会被AsyncUncaughtExceptionHandler处理,我们需要在方法中捕获异常并处理*或者当调用者调用Future.get时捕获异常并处理*@parami*@return*/@AsyncpublicFutureasyncInvokeReturnFuture(inti){log.info("asyncInvokeReturnFuture,parementer={}",i);未来未来;尝试{线程。睡眠(1000*1);future=newAsyncResult("成功:"+i);抛出新的IllegalArgumentException("a");}catch(InterruptedExceptione){future=newAsyncResult("error");}catch(IllegalArgumentExceptione){future=newAsyncResult("error-IllegalArgumentException");}returnfuture;}有返回值CompletableFuture不使用@Async注解调用CompletableFuture,可以实现调用系统线程池处理业务的功能。JDK5增加了一个Future接口来描述异步计算的结果。Future和相关的使用方法虽然提供了异步执行任务的能力,但是获取结果非常不方便,只能通过阻塞或者轮询的方式获取任务的结果。阻塞方式显然违背了我们异步编程的初衷,轮询方式会消耗不必要的CPU资源,无法及时得到计算结果。CompletionStage代表了异步计算过程中的某个阶段。一个阶段完成后,可能会触发另一个阶段。Stage的计算执行可以是Function、Consumer或Runnable。例如:stage.thenApply(x->square(x)).thenAccept(x->System.out.print(x)).thenRun(()->System.out.println())一个阶段的执行可能是单个阶段完成触发,也可能是多个阶段一起触发。在Java8中,CompletableFuture提供了非常强大的Future扩展功能,可以帮助我们简化异步编程的复杂度,提供函数式编程能力。计算结果可以通过回调进行处理,同时也提供了CompletableFutures转换和合并的方法。它可能代表一个明确完成的Future,也可能代表一个完成阶段(CompletionStage),支持在计算完成后触发一些功能或执行某些动作。它实现了Future和CompletionStage接口/***数据查询线程池*/privatestaticfinalThreadPoolExecutorSELECT_POOL_EXECUTOR=newThreadPoolExecutor(10,20,5000,TimeUnit.MILLISECONDS,newLinkedBlockingQueue<>(1024),newThreadFactoryBuilder().setNameFormat("selectThreadPoolExecutor-%d").build());//tradeMapper.countTradeLog(tradeSearchBean)方法表示获取数量,返回值为int//获取CompletableFuturecountFuture=CompletableFuture的总数。supplyAsync(()->tradeMapper.countTradeLog(tradeSearchBean),SELECT_POOL_EXECUTOR);//同步阻塞CompletableFuture.allOf(countFuture).join();//获取结果intcount=countFuture.get();默认线程池的缺点是在线程池应用中,参考阿里巴巴java开发规范:不允许使用Executors创建线程池,不允许使用系统默认的线程池。推荐使用ThreadPoolExecutor。这种处理方式可以让开发工程师明确线程池的运行规则,避免资源浪费。疲惫的风险。Executors:newFixedThreadPool和newSingleThreadExecutor的各个方法的缺点:主要问题是累积的请求处理队列可能会消耗非常大的内存,甚至OOM。newCachedThreadPool和newScheduledThreadPool:主要问题是最大线程数为Integer.MAX_VALUE,可能会创建非常多的线程,甚至OOM。@Async默认异步配置使用SimpleAsyncTaskExecutor。默认情况下,线程池为每个任务创建一个线程。如果系统中不断地创建线程,系统最终会占用过多的内存而导致OutOfMemoryError错误。针对线程创建的问题,SimpleAsyncTaskExecutor提供了限流机制,由concurrencyLimit属性控制。当concurrencyLimit>=0时,开启限流机制,默认关闭限流机制,即concurrencyLimit=-1。关闭时,将不断创建新的。线程来处理任务。基于默认配置,SimpleAsyncTaskExecutor严格来说不是线程池,无法实现线程复用的功能。@Async将自定义线程池应用于自定义线程池,可以更细粒度地控制系统中的线程池,方便调整线程池大小配置,线程执行异常控制和处理。在设置系统自定义线程池替换默认线程池时,虽然可以设置多种方式,但是替换默认线程池后只会生成一个线程池(不能设置多个类继承AsyncConfigurer)。自定义线程池有以下几种模式:重新实现接口AsyncConfigurer继承AsyncConfigurerSupport配置,将内置的任务执行器替换为自定义的TaskExecutor通过查看Spring源码中@Async的默认调用规则,实现类首先查找源码中的AsyncConfigurer接口。实现这个接口的类是AsyncConfigurerSupport。但是默认配置的线程池和异步处理方法都是空的,所以无论是继承还是重新实现接口,都需要指定一个线程池。并重新实现publicExecutorgetAsyncExecutor()方法。实际接口AsyncConfigurer@ConfigurationpublicclassAsyncConfigurationimplementsAsyncConfigurer{@Bean("kingAsyncExecutor")publicThreadPoolTask??Executorexecutor(){ThreadPoolTask??Executorexecutor=newThreadPoolTask??Executor();int核心池大小=10;executor.setCorePoolSize(corePoolSize);intmaxPoolSize=50;执行人。setMaxPoolSize(maxPoolSize);intqueueCapacity=10;executor.setQueueCapacity(queueCapacity);executor.setRejectedExecutionHandler(newThreadPoolExecutor.CallerRunsPolicy());StringthreadNamePrefix="kingDeeAsyncExecutor-";executor.setThreadNamePrefix(threadNamePrefix);executor.setWaitForTasksToCompleteOnShutdown(true);//使用自定义的跨线程请求级线程工厂类19intawaitTerminationSeconds=5;executor.setAwaitTerminationSeconds(awaitTerminationSeconds);executor.initialize();回归执行人;}@Override公共执行器getAsyncExecutor(){返回执行器();}@OverridepublicAsyncUncaughtExceptionHandlergetAsyncUncaughtExceptionHandler(){return(ex,method,params)->ErrorLogger.getInstance().log(String.format("执行异常步骤'%s'",method),ex);}}继承AsyncConfigurerSupport@Configuration@EnableAsyncclassSpringAsyncConfigurerextendsAsyncConfigurerSupport{@BeanpublicThreadPoolTask??ExecutorasyncExecutor(){ThreadPoolTask??ExecutorthreadPool=newThreadPoolTask??Executor();threadPool.setCorePoolSize(3);threadPool.setMaxPoolSize(3);threadPool.setWaitForTasksToCompleteOnShutdown(true);threadPool.setAwaitTerminationSeconds(60*15);返回线程池;}@OverridepublicExecutorgetAsyncExecutor(){returnasyncExecutor;}@OverridepublicAsyncUncaughtExceptionHandlergetAsyncUncaughtExceptionHandler(){return(ex,method,params)->ErrorLogger.getInstance().log(String.format("执行异步任务'%s'",method),ex);}}配置自定义TaskExecutor由于源码中AsyncConfigurer默认的线程池是空的,所以Spring首先通过beanFactory.getBean(TaskExecutor.class),没有配置时,通过beanFactory.getBean(DEFAULT_TASK_EXECUTOR_BEAN_NAME,Executor.class),查询是否有默认名称为TaskExecutor的线程池,所以可以在项目中定义一个名为TaskExecutor的bean来生成默认线程池.也可以不指定线程池声明一个线程池的名称,底层本身是基于TaskExecutor.class.例如:Executor.class:ThreadPoolExecutorAdapter->ThreadPoolExecutor->AbstractExecutorService->ExecutorService->Executor,最后一层是Executor.class,替换后使用默认线程池时,需要设置默认线程池名称为TaskExecutorTaskExecutor.class:ThreadPoolTask??Executor->SchedulingTaskExecutor->AsyncTaskExecutor->TaskExecutor。最后一层是TaskExecutor.class。替换默认线程池时,不需要指定线程池名称。@EnableAsync@ConfigurationpublicclassTaskPoolConfig{@Bean(name=AsyncExecutionAspectSupport.DEFAULT_TASK_EXECUTOR_BEAN_NAME)publicExecutortaskExecutor(){ThreadPoolTask??Executorexecutor=newThreadPoolTask??Executor();//最大核心线程池数executor.setCorePoolScuize)setMaxPoolSize(20);//队列容量executor.setQueueCapacity(200);//活动时间executor.setKeepAliveSeconds(60);//线程名称前缀executor.setThreadNamePrefix("taskExecutor-");executor.setRejectedExecutionHandler(newThreadPoolExecutor.CallerRunsPolicy());回归执行人;}@Bean(name="new_task")publicExecutortaskExecutor(){ThreadPoolTask??Executorexecutor=newThreadPoolTask??Executor();//核心线程池大小executor.setCorePoolSize(10);//最大线程数executor.setMaxPoolSize(20);//队列容量executor.setQueueCapacity(200);//活动时间executor.setKeepAliveSeconds(60);//线程名称前缀executor.setThreadNamePrefix("taskExecutor-");executor.setRejectedExecutionHandler(newThreadPoolExecutor.CallerRunsPolicy());回归执行人;}}多线程池@Async注解,使用系统默认或自定义的线程池(而不是默认的线程池)项目中可以设置多个线程池。异步调用时,指定要调用的线程池名称,如@Async("new_task")。@Async部分重要源码解析源码——获取线程池方法源码——设置默认线程池defaultExecutor,默认为空,重新实现接口AsyncConfigurer的getAsyncExecutor()时,可以设置默认线程水池。源码-当找不到项目中设置的默认线程池时,使用spring默认的线程池。近期热点文章推荐:1.1000+Java面试题及答案(2021最新版)2.厉害了!Java协程来了。..3.玩大!Log4j2.x再次爆发。..4、SpringBoot2.6正式发布,一大波新特性。.5.《Java开发手册(嵩山版)》最新发布,赶快下载吧!感觉不错,别忘了点赞+转发!