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

Spring中的@Schedule和@Async注解实现细粒度的定时任务

时间:2023-04-02 09:17:38 Java

前言:并发执行多个任务,异步执行同一个任务需要自定义线程池实现对线程更细粒度的控制,如何实现你做吗?执行?本文使用@Schedule和@Async完成。它面向初级和中级Java开发工程师。阅读时间约为10分钟。首先只有@Schedule发现只有同一个线程才会阻塞线程。@ComponentpublicclassCurrThreadRun{@Scheduled(fixedRate=3000)publicvoidscheduledTask()throwsInterruptedException{SimpleDateFormatsdf=newSimpleDateFormat();sdf.applyPattern("yyyy-MM-ddHH:mm:ssa");日期date=newDate();System.out.println(sdf.format(date)+Thread.currentThread().getName()+"===1run");线程.睡眠(6*1000);日期=新日期();System.out.println(sdf.format(date)+Thread.currentThread().getName()+"===1end");}@Scheduled(fixedRate=3000)publicvoidscheduledTask2()throwsInterruptedException{SimpleDateFormatsdf=newSimpleDateFormat();sdf.applyPattern("yyyy-MM-ddHH:mm:ssa");日期date=newDate();System.out.println(sdf.format(date)+Thread.currentThread().getName()+"===2run");线程.睡眠(6*1000);d吃=新日期();System.out.println(sdf.format(date)+Thread.currentThread().getName()+"===2end");}}控制台输出如下,可以看到@sheduleisasynchronoustask2022-03-0616:26:39.744INFO63252---[main]o.s.b.w.embedded.tomcat.TomcatWebServer:Tomcatstartedonport(s):8091(http)withcontextpath''2022-03-0616:26:39.749INFO63252---[main]s.a.ScheduledAnnotationBeanPostProcessor:上下文中存在多个TaskSchedulerbean,没有一个被命名为“taskScheduler”.将其中一个标记为主要的或将其命名为“taskScheduler”(可能是别名);或者实现SchedulingConfigurer接口并在configureTasks()回调中显式调用ScheduledTaskRegistrar#setScheduler:[scheduledTaskTwo,scheduledTaskOne]2022-03-0616:26:39pmpool-1-1-thread===1run2022-03-0616:26:39.753INFO63252---[main]c.r.s.SpringbootDemoApplication:在1.198中启动了SpringbootDemoApplication秒(JVM运行1.407)2022-03-0616:26:45pmpool-1-thread-1===1end2022-03-0616:26:45pmpool-1-thread-1===2run2022-03-0616:26:51pmpool-1-thread-1===2end2022-03-0616:26:51pmpool-1-thread-1===1运行打开了ThreadPoolTask??Scheduler源找到的代码(设置ScheduledExecutorService的池大小。默认值为1。可以在运行时修改此设置,例如通过JMX。)默认打开的线程数是一个publicvoidsetPoolSize(intpoolSize){Assert.isTrue(poolSize>0,"'poolSize'必须为1或更高");if(this.scheduledExecutorinstanceofScheduledThreadPoolExecutor){((ScheduledThreadPoolExecutor)this.scheduledExecutor).setCorePoolSize(poolSize);}this.poolSize=poolSize;所以我们的代码有以下变化。@BeanpublicTaskSchedulertaskScheduler(){ThreadPoolTask??SchedulertaskScheduler=newThreadPoolTask??Scheduler();taskScheduler.setPoolSize(2);//我这里设置的线程数是2,大家可以根据需要调整返回的taskScheduler;并发执行,但是同一个任务的异步执行还没有实现。这个要求就更简单了。添加@Async后,我们可以实现同一个任务的异步执行。@ComponentpublicclassCurrThreadRun{@Async@Scheduled(fixedRate=3000)publicvoidscheduledTask()throwsInterruptedException{SimpleDateFormatsdf=newSimpleDateFormat();sdf.applyPattern("yyyy-MM-ddHH:mm:ssa");日期date=newDate();System.out.println(sdf.format(date)+Thread.currentThread().getName()+"===1run");线程.睡眠(6*1000);日期=新日期();System.out.println(sdf.format(date)+Thread.currentThread().getName()+"===1end");}@Async@Scheduled(fixedRate=3000)publicvoidscheduledTask2()throwsInterruptedException{SimpleDateFormatsdf=newSimpleDateFormat();sdf.applyPattern("yyyy-MM-ddHH:mm:ssa");日期date=newDate();System.out.println(sdf.format(date)+Thread.currentThread().getName()+"===2run");线程.睡眠(6*1000);日期=新日期();System.out.println(sdf.format(date)+Thread.currentThread().getName()+"===2end");}@BeanpublicTaskSchedulertaskScheduler(){ThreadPoolTask??SchedulertaskScheduler=newThreadPoolTask??Scheduler();taskScheduler.setPoolSize(2);//我这里设置的线程数是2,大家可以根据需要调整返回的taskScheduler;}}输出如下2022-03-0616:40:55.794INFO63750---[main]o.s.b.w.embedded.tomcat.TomcatWebServer:Tomcatstartedonport(s):8091(http)withcontextpath''2022-03-0616:40:55.799INFO63750---[taskScheduler-1].s.a.AnnotationAsyncExecutionInterceptor:在上下文中找到多个TaskExecutorbean,但没有被命名为“任务执行器”。将其中一个标记为主要或将其命名为“taskExecutor”(可能作为别名)以便将其用于异步处理:[taskScheduler,scheduledTaskTwo,scheduledTaskOne]2022-03-0616:40:55PMSimpleAsyncTaskExecutor-2===2run2022-03-0616:40:55PMSimpleAsyncTaskExecutor-1===1run2022-03-0616:40:55.801INFO63750---[main]c.r.s.SpringbootDemoApplication:在1.261秒内启动SpringbootDemoApplication(JVM运行1.581)2022-03-0616:40:58PMSimpleAsyncTaskExecutor-3===1run2022-03-0616:40:58PMSimpleAsyncTaskExecutor-4===2run2022-03-0616:41:01pmSimpleAsyncTaskExecutor-6===2run2022-03-0616:41:01pmSimpleAsyncTaskExecutor-5===1run2022-03-0616:41:01pmSimpleAsyncTaskExecutor-2===2endbutThreadpoolnameofthisoutput比较难理解,因为@Async是Async使用默认的SimpleAsyncTaskExecutor作为线程池,所以为了日志的可读性,好奇的看了看线程池SimpleAsyncTaskExecutor,在文件开头出现了一个注释TaskExecutor为每个任务启动一个新线程的实现,execu异步调整它。支持通过“concurrencyLimit”bean属性限制并发线程。默认情况下,并发线程数是无限的。注意:此实现不重用线程!考虑线程池TaskExecutor实现而不是用于执行大量短期任务TaskExecutor实现为每个任务启动一个新线程,异步执行。它支持通过“concurrencyLimit”bean属性限制并发线程。默认情况下,并发线程数是无限的。注意:此实现不重用线程!考虑一个线程池TaskExecutor实现,特别是用于执行大量短期任务,因此线程池默认为每个任务创建一个线程。如果系统中不断地创建线程,系统最终会占用过多的内存而导致OutOfMemoryError错误。所以严格来说,这个人不是线程池,我们需要重新设置。那么@Async是如何配置线程池的,我会另外写一篇@Async配置线程池的文章来描述。为了满足前言的要求,我简单的配置了@Async和ThreadPoolTask??Scheduler,整个代码如下图所示。@ComponentpublicclassCurrThreadRun{@Async("scheduledTaskOne")@Scheduled(fixedRate=3000)publicvoidscheduledTask()throwsInterruptedException{SimpleDateFormatsdf=newSimpleDateFormat();sdf.applyPattern("yyyy-MM-ddHH:mm:ssa");日期date=newDate();System.out.println(sdf.format(date)+Thread.currentThread().getName()+"===1run");线程.睡眠(6*1000);日期=新日期();System.out.println(sdf.format(date)+Thread.currentThread().getName()+"===1end");}@Async("scheduledTaskTwo")@Scheduled(fixedRate=3000)publicvoidscheduledTask2()throwsInterruptedException{SimpleDateFormatsdf=newSimpleDateFormat();sdf.applyPattern("yyyy-MM-ddHH:mm:ssa");日期date=newDate();System.out.println(sdf.format(date)+Thread.currentThread().getName()+"===2run&q不;);线程.睡眠(6*1000);日期=新日期();System.out.println(sdf.format(date)+Thread.currentThread().getName()+"===2end");}@BeanThreadPoolTask??SchedulerscheduledTaskTwo(){ThreadPoolTask??SchedulerthreadPoolTask??Scheduler=newThreadPoolTask??Scheduler();threadPoolTask??Scheduler.setPoolSize(2);threadPoolTask??Scheduler.setAwaitTerminationSeconds(60);threadPoolTask??Scheduler.setThreadNamePrefix("TASK_SCHEDULER_SECOND-AAA-");返回threadPoolTask??Scheduler;}@BeanThreadPoolTask??SchedulerscheduledTaskOne(){ThreadPoolTask??SchedulerthreadPoolTask??Scheduler=newThreadPoolTask??Scheduler();threadPoolTask??Scheduler.setPoolSize(2);threadPoolTask??Scheduler.setThreadNamePrefix("TASK_SCHEDULER_SECOND-BBB-");返回threadPoolTask??Scheduler;}}得到了输出的控制台如下所示:2022-03-0617:10:18.509INFO64755---[main]o.s.b.w.embedded.tomcat.TomcatWebServer:Tomcat启动端口:8091(http)withcontextpath''2022-03-0617:10:18.513INFO64755---[main]s.a.ScheduledAnnotationBeanPostProcessor:不止一个TaskSchedulerbean存在于上下文中,并且没有一个名为“taskScheduler”。将其中一个标记为主要的或将其命名为“taskScheduler”(可能是别名);或实现SchedulingConfigurer接口并在configureTasks()回调中显式调用ScheduledTaskRegistrar#setScheduler:[scheduledTaskTwo,scheduledTaskOne]2022-03-0617:10:18.516INFO64755---[main]c.r.s.SpringbootDemoApplication:在1.391秒内启动了SpringbootDemoApplication(JVMrunningfor1.613)2022-03-0617:10:18下午TASK_SCHEDULER_SECOND-BBB-1===1run2022-03-0617:10:18下午TASK_SCHEDULER_SECOND-AAA-1===2run2022-03-0617:10:21下午TASK_SCHEDULER_SECOND-BBB-2===1run2022-03-0617:10:21下午TASK_SCHEDULER_SECOND-AAA-2===2run2022-03-0617:10:24PMTASK_SCHEDULER_SECOND-BBB-1===1end2022-03-0617:10:24PMTASK_SCHEDULER_SECOND-AAA-1===2end2022-03-0617:10:24下午TASK_SCHEDULER_SECOND-BBB-1===1run2022-03-0617:10:24PMTASK_SCHEDULER_SECOND-AAA-1===2run参考SpringBoot@Scheduled注解用法:同步/异步同任务和多任务并发执行Spring使用@Async注解