Are @Schedule scheduled tasks executed in parallel?

@Schedule is a common timed task annotation. It is generally used in business logic that needs to be executed regularly. It is mostly used for single-machine tasks. If it is used in distributed tasks, it is necessary to ensure data consistency through distributed locks.

If there are multiple scheduled tasks defined by the @Schedule annotation, are they executed concurrently or serially?

The timed tasks defined by @Schedule should also have a thread pool, such as ScheduleThreadPool in the four common thread pools

Four common thread pools:

  1. SingleThreadPool: single thread thread pool
  2. FixedThreadPool: Thread pool for fixed threads
  3. ScheduleThreadPool: Thread pool for scheduled tasks
  4. CachedThreadPool: A thread pool with an infinite number of cached threads but an infinite number of threads

So is it to execute multiple timing tasks concurrently or only one thread executes timing tasks serially?

If it is executed serially, then there will be serious problems! Scheduled tasks cannot be executed on time and there is a risk of blocking

So how to make the timed tasks defined by @Schedule execute concurrently with multiple threads?

test

In the same class, define multiple timing tasks through @Schedule to check whether multiple timing tasks are executed using the same thread and whether they are executed in parallel

  • sample code
@Component
public class ScheduleTest {

    @Scheduled(cron = "0/30 * * * * ?")
    public void task1() {
        System.out.println("task1 start");
        System.out.println(Thread.currentThread().getId() + "  " + Thread.currentThread().getName());
    }


    @Scheduled(cron = "0/30 * * * * ?")
    public void task2() {
        System.out.println("task2 start");
        System.out.println(Thread.currentThread().getId() + "  " + Thread.currentThread().getName());
    }

    @Scheduled(cron = "0/50 * * * * ?")
    public void task3() {
        System.out.println("task3 start");
        System.out.println(Thread.currentThread().getId() + "  " + Thread.currentThread().getName());
    }

}
  • output
task2 start
40  scheduling-1
task1 start
40  scheduling-1
task3 start
40  scheduling-1

As you can see, the three tasks use the same thread

  • test again

By writing an infinite loop in a certain task, let this task continue to execute, and then see if other scheduled tasks will still execute

   @Scheduled(cron = "0/30 * * * * ?")
    public void task1() {
        System.out.println("task1 start");
        System.out.println(Thread.currentThread().getId() + "  " + Thread.currentThread().getName());
    }


    @Scheduled(cron = "0/30 * * * * ?")
    public void task2() {
        while (true) {
            System.out.println("task2 start");
            System.out.println(Thread.currentThread().getId() + "  " + Thread.currentThread().getName());
        }
    }

It can be seen from the output that the task of task1 has not been executed, and the task of task2 has been executed.

Source code analysis

The processing class of the @Schedule annotation is in the class org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor

  1. In this method, it indicates the business logic to be executed after the bean is initialized

  1. In the this.registrar.afterPropertiesSet(); method at the bottom of the finishRegistration method, it means to start calling the post-processor.

  2. The post-processor starts to execute all the scheduled tasks scanned above, org.springframework.scheduling.config.ScheduledTaskRegistrar#scheduleTasks

  3. As you can see from the scheduleTasks method, during execution, it will check whether there is a custom thread pool, if not, then a SingleThreadSchedulePool will be created as a thread pool for execution

    [External link image transfer failed, the source site may have anti-leech mechanism, it is recommended to save the image and upload it directly (img-vLHH8D3n-1651588669675)(https://cdn.jsdelivr.net/gh/chenliang15405/picture/hub/cs /image-20220503221258957.png)]

Seeing this, it is already obvious that when using, there is no custom thread pool, so when executing tasks, Spring will automatically create a thread pool to execute, but the default thread pool is a core thread of 1 single-threaded thread pool

solution

  • The first: global configuration

    This method is equivalent to the aspect method. For the unified timing task pair processing, there is no need to pay attention to the thread pool configuration of each timing task.

    @Configuration
    @EnableScheduling
    public class ScheduleConfig implements SchedulingConfigurer {
    
    
        @Override
        public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
            taskRegistrar.setScheduler(Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors()));
        }
    
    }
    
  • The second: custom thread pool

    In this way, different thread pools can be configured according to the business logic and the importance of timed tasks, and different tasks can be isolated without affecting each other. The timed tasks configured in this way are more flexible

    • configuration class

      @EnableAsync
      @Configuration
      public class AsyncScheduleConfig {
      
          @Bean("scheduleExecutor")
          public Executor myAsync() {
              ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
              //maximum number of threads
              executor.setMaxPoolSize(100);
              //number of core threads
              executor.setCorePoolSize(10);
              //The size of the task queue
              executor.setQueueCapacity(10);
              //thread prefix
              executor.setThreadNamePrefix("async-schedule-");
              //thread survival time
              executor.setKeepAliveSeconds(60);
              /**
               * Deny Handling Policy
               * CallerRunsPolicy(): Let the caller thread run, such as the main thread.
               * AbortPolicy(): Throws an exception directly.
               * DiscardPolicy(): Discard directly.
               * DiscardOldestPolicy(): Discard the oldest task in the queue.
               */
              executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
              //thread initialization
              executor.initialize();
              return executor;
          }
      
      }
      
    • use

      Just add the @Async annotation to the annotation of the scheduled task, and explicitly specify the custom thread pool name.

      @Component
      public class ScheduleTest {
      
          @Async("scheduleExecutor")
          @Scheduled(cron = "0/30 * * * * ?")
          public void task1() {
              System.out.println("task1 start");
              System.out.println(Thread.currentThread().getId() + "  " + Thread.currentThread().getName());
          }
      
          @Async("scheduleExecutor")
          @Scheduled(cron = "0/30 * * * * ?")
          public void task2() {
              System.out.println("task2 start");
              System.out.println(Thread.currentThread().getId() + "  " + Thread.currentThread().getName());
          }
      
          @Async("scheduleExecutor")
          @Scheduled(cron = "0/50 * * * * ?")
          public void task3() {
              System.out.println("task3 start");
              System.out.println(Thread.currentThread().getId() + "  " + Thread.currentThread().getName());
          }
      
      }
      

Tags: Java Spring Spring Boot Distribution

Posted by ssailer on Tue, 03 May 2022 20:21:48 +0300