乐趣区

关于spring:Spring中Schedule和Async注解实现细粒度定时任务

前言: 多任务并发执行, 同一工作的异步执行并且须要自定义线程池从而做到对线程得更加细粒度的控
制, 请问你该如何实现?  

本文应用了 @Schedule 和 @Async 配合实现.
面向群体为初、中级 Java 开发工程师. 浏览工夫 10min 左右。

首先仅仅 @Schedule 发现用的是仅仅同一个线程会产生线程的阻塞.

@Component
public class CurrThreadRun {@Scheduled(fixedRate = 3000)
    public void scheduledTask() throws InterruptedException {SimpleDateFormat sdf = new SimpleDateFormat();
        sdf.applyPattern("yyyy-MM-dd HH:mm:ss a");
        Date date = new Date();
        System.out.println(sdf.format(date) + Thread.currentThread().getName() + "===1 run");
        Thread.sleep(6 * 1000);
        date = new Date();
        System.out.println(sdf.format(date) + Thread.currentThread().getName() + "===1 end");
    }

    @Scheduled(fixedRate = 3000)
    public void scheduledTask2() throws InterruptedException {SimpleDateFormat sdf = new SimpleDateFormat();
        sdf.applyPattern("yyyy-MM-dd HH:mm:ss a");
        Date date = new Date();
        System.out.println(sdf.format(date) + Thread.currentThread().getName() + "===2 run");
        Thread.sleep(6 * 1000);
        date = new Date();
        System.out.println(sdf.format(date) + Thread.currentThread().getName() + "===2 end");
    }
}

控制台的输入为以下内容, 能够看到 @shedule 是个同步执行的工作

2022-03-06 16:26:39.744  INFO 63252 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8091 (http) with context path ''2022-03-06 16:26:39.749  INFO 63252 --- [main] s.a.ScheduledAnnotationBeanPostProcessor : More than one TaskScheduler bean exists within the context, and none is named'taskScheduler'. Mark one of them as primary or name it'taskScheduler' (possibly as an alias); or implement the SchedulingConfigurer interface and call ScheduledTaskRegistrar#setScheduler explicitly within the configureTasks() callback: [scheduledTaskTwo, scheduledTaskOne]
2022-03-06 16:26:39 下午 pool-1-thread-1===1 run
2022-03-06 16:26:39.753  INFO 63252 --- [main] c.r.s.SpringbootDemoApplication          : Started SpringbootDemoApplication in 1.198 seconds (JVM running for 1.407)
2022-03-06 16:26:45 下午 pool-1-thread-1===1 end
2022-03-06 16:26:45 下午 pool-1-thread-1===2 run
2022-03-06 16:26:51 下午 pool-1-thread-1===2 end
2022-03-06 16:26:51 下午 pool-1-thread-1===1 run

关上了 ThreadPoolTaskScheduler 源码发 0 现 (Set the ScheduledExecutorService’s pool size. Default is 1.
This setting can be modified at runtime, for example through JMX.) 默认开启的线程数量为一。

    public void setPoolSize(int poolSize) {Assert.isTrue(poolSize > 0, "'poolSize' must be 1 or higher");
        if (this.scheduledExecutor instanceof ScheduledThreadPoolExecutor) {((ScheduledThreadPoolExecutor) this.scheduledExecutor).setCorePoolSize(poolSize);
        }
        this.poolSize = poolSize;
    }

于是咱们的代码又有了以下的改变。

    @Bean
    public TaskScheduler taskScheduler() {ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        taskScheduler.setPoolSize(2);// 我这里设置的线程数是 2, 能够依据需要调整
        return taskScheduler;
    }

做到这里咱们实现了多任务并发执行, 然而同一工作异步执行还没有实现. 这个需要就更加简略了.
加上 @Async 之后, 咱们能够实现了同一工作的异步执行.

@Component
public class CurrThreadRun {
    @Async
    @Scheduled(fixedRate = 3000)
    public void scheduledTask() throws InterruptedException {SimpleDateFormat sdf = new SimpleDateFormat();
        sdf.applyPattern("yyyy-MM-dd HH:mm:ss a");
        Date date = new Date();
        System.out.println(sdf.format(date) + Thread.currentThread().getName() + "===1 run");
        Thread.sleep(6 * 1000);
        date = new Date();
        System.out.println(sdf.format(date) + Thread.currentThread().getName() + "===1 end");
    }

    @Async
    @Scheduled(fixedRate = 3000)
    public void scheduledTask2() throws InterruptedException {SimpleDateFormat sdf = new SimpleDateFormat();
        sdf.applyPattern("yyyy-MM-dd HH:mm:ss a");
        Date date = new Date();
        System.out.println(sdf.format(date) + Thread.currentThread().getName() + "===2 run");
        Thread.sleep(6 * 1000);
        date = new Date();
        System.out.println(sdf.format(date) + Thread.currentThread().getName() + "===2 end");
    }

    @Bean
    public TaskScheduler taskScheduler() {ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        taskScheduler.setPoolSize(2);// 我这里设置的线程数是 2, 能够依据需要调整
        return taskScheduler;
    }
}

输入为以下

2022-03-06 16:40:55.794  INFO 63750 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8091 (http) with context path ''2022-03-06 16:40:55.799  INFO 63750 --- [taskScheduler-1] .s.a.AnnotationAsyncExecutionInterceptor : More than one TaskExecutor bean found within the context, and none is named'taskExecutor'. Mark one of them as primary or name it'taskExecutor' (possibly as an alias) in order to use it for async processing: [taskScheduler, scheduledTaskTwo, scheduledTaskOne]
2022-03-06 16:40:55 下午 SimpleAsyncTaskExecutor-2===2 run
2022-03-06 16:40:55 下午 SimpleAsyncTaskExecutor-1===1 run
2022-03-06 16:40:55.801  INFO 63750 --- [main] c.r.s.SpringbootDemoApplication          : Started SpringbootDemoApplication in 1.261 seconds (JVM running for 1.581)
2022-03-06 16:40:58 下午 SimpleAsyncTaskExecutor-3===1 run
2022-03-06 16:40:58 下午 SimpleAsyncTaskExecutor-4===2 run
2022-03-06 16:41:01 下午 SimpleAsyncTaskExecutor-6===2 run
2022-03-06 16:41:01 下午 SimpleAsyncTaskExecutor-5===1 run
2022-03-06 16:41:01 下午 SimpleAsyncTaskExecutor-2===2 end

然而这个输入的线程池名称让人难以了解,因为 @Async 是 Async 用的是默认的 SimpleAsyncTaskExecutor 作为线程池, 所以 为了日志的可浏览性,
带着好奇心看了一下 SimpleAsyncTaskExecutor 这个线程池, 文件结尾就呈现了一正文

TaskExecutor implementation that fires up a new Thread for each task, executing it asynchronously.
Supports limiting concurrent threads through the "concurrencyLimit" bean property. By default, the number of concurrent threads is unlimited.
NOTE: This implementation does not reuse threads! Consider a thread-pooling TaskExecutor implementation instead, in particular for executing a large number of short-lived tasks  
TaskExecutor 实现为每个工作启动一个新线程,异步执行它。反对通过“concurrencyLimit”bean 属性限度并发线程。默认状况下,并发线程数是有限的。留神:此实现不重用线程!思考一个线程池 TaskExecutor 实现,特地是用于执行大量短期工作

所以该线程池默认来一个工作创立一个线程,若零碎中一直的创立线程,最终会导致系统占用内存过高,引发 OutOfMemoryError 谬误。所以严格意义上说这个人并不是啥线程池咱们要从新设置一下。

那么 @Async 是如何配置线程池的呢,这个我会另外再写一篇 @Async 配置线程池的文章进行形容.

为了满足前言的需要,我简略将 @Async 配置了 ThreadPoolTaskScheduler, 全副的代码如下图所示。

@Component
public class CurrThreadRun {@Async("scheduledTaskOne")
    @Scheduled(fixedRate = 3000)
    public void scheduledTask() throws InterruptedException {SimpleDateFormat sdf = new SimpleDateFormat();
        sdf.applyPattern("yyyy-MM-dd HH:mm:ss a");
        Date date = new Date();
        System.out.println(sdf.format(date) + Thread.currentThread().getName() + "===1 run");
        Thread.sleep(6 * 1000);
        date = new Date();
        System.out.println(sdf.format(date) + Thread.currentThread().getName() + "===1 end");
    }

    @Async("scheduledTaskTwo")
    @Scheduled(fixedRate = 3000)
    public void scheduledTask2() throws InterruptedException {SimpleDateFormat sdf = new SimpleDateFormat();
        sdf.applyPattern("yyyy-MM-dd HH:mm:ss a");
        Date date = new Date();
        System.out.println(sdf.format(date) + Thread.currentThread().getName() + "===2 run");
        Thread.sleep(6 * 1000);
        date = new Date();
        System.out.println(sdf.format(date) + Thread.currentThread().getName() + "===2 end");
    }

    @Bean
    ThreadPoolTaskScheduler scheduledTaskTwo() {ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
        threadPoolTaskScheduler.setPoolSize(2);
        threadPoolTaskScheduler.setAwaitTerminationSeconds(60);
        threadPoolTaskScheduler.setThreadNamePrefix("TASK_SCHEDULER_SECOND-AAA-");
        return threadPoolTaskScheduler;
    }

    @Bean
    ThreadPoolTaskScheduler scheduledTaskOne() {ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
        threadPoolTaskScheduler.setPoolSize(2);
        threadPoolTaskScheduler.setThreadNamePrefix("TASK_SCHEDULER_SECOND-BBB-");
        return threadPoolTaskScheduler;
    }
}

失去了输入的控制台如下所示:

2022-03-06 17:10:18.509  INFO 64755 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8091 (http) with context path ''2022-03-06 17:10:18.513  INFO 64755 --- [main] s.a.ScheduledAnnotationBeanPostProcessor : More than one TaskScheduler bean exists within the context, and none is named'taskScheduler'. Mark one of them as primary or name it'taskScheduler' (possibly as an alias); or implement the SchedulingConfigurer interface and call ScheduledTaskRegistrar#setScheduler explicitly within the configureTasks() callback: [scheduledTaskTwo, scheduledTaskOne]
2022-03-06 17:10:18.516  INFO 64755 --- [main] c.r.s.SpringbootDemoApplication          : Started SpringbootDemoApplication in 1.391 seconds (JVM running for 1.613)
2022-03-06 17:10:18 下午 TASK_SCHEDULER_SECOND-BBB-1===1 run
2022-03-06 17:10:18 下午 TASK_SCHEDULER_SECOND-AAA-1===2 run
2022-03-06 17:10:21 下午 TASK_SCHEDULER_SECOND-BBB-2===1 run
2022-03-06 17:10:21 下午 TASK_SCHEDULER_SECOND-AAA-2===2 run
2022-03-06 17:10:24 下午 TASK_SCHEDULER_SECOND-BBB-1===1 end
2022-03-06 17:10:24 下午 TASK_SCHEDULER_SECOND-AAA-1===2 end
2022-03-06 17:10:24 下午 TASK_SCHEDULER_SECOND-BBB-1===1 run
2022-03-06 17:10:24 下午 TASK_SCHEDULER_SECOND-AAA-1===2 run

参考资料

  1. SpringBoot @Scheduled 注解应用: 同步 / 异步同一工作及多任务并发执行
  2. Spring 应用 @Async 注解
退出移动版