Home  >  Article  >  Java  >  How to use multi-threading of scheduled tasks @Scheduled in SpringBoot

How to use multi-threading of scheduled tasks @Scheduled in SpringBoot

WBOY
WBOYforward
2023-05-14 19:37:041537browse

    1. Introduction to the @Scheduled annotation

    @Scheduled is an annotation in the Spring framework. It can be used to configure scheduled tasks so that the method can be used as specified Executed regularly at intervals. When using this annotation, we can specify the execution time, cycle period, number of concurrency and other parameters of the task to realize the function of scheduled tasks. In Spring Boot, the @Scheduled annotation can be applied directly to methods.

    2. Multi-threading mechanism of @Scheduled

    In Spring Boot, the @Scheduled annotation is implemented based on Java's ThreadPoolExecutor and ScheduledThreadPoolExecutor. When we configure a scheduled task, Spring Boot will first create a ScheduledThreadPoolExecutor thread pool and add the scheduled task to the thread pool to wait for execution. Then, after the specified time arrives, the thread pool will allocate a thread to execute the scheduled task. If the scheduled task has not been executed yet, when the next cycle arrives, the thread pool will again allocate a thread for execution to the task. In this way, @Scheduled can very conveniently implement periodic scheduled tasks implemented by Java's ThreadPoolExecutor and ScheduledThreadPoolExecutor. When we configure a scheduled task, Spring Boot will first create a ScheduledThreadPoolExecutor thread pool and add the scheduled task to the thread pool to wait for execution. Then, after the specified time arrives, the thread pool will allocate a thread to execute the scheduled task. If the scheduled task has not been executed yet, when the next cycle arrives, the thread pool will again allocate a thread for execution to the task. In this way, @Scheduled can easily implement periodic scheduled tasks.

    3. Multi-threading issues of @Scheduled

    Although the @Scheduled annotation is very convenient, it also has some multi-threading issues, mainly reflected in the following two aspects:

    1. When the scheduled task is not completed, subsequent tasks may be affected

    When using the @Scheduled annotation, it is easy for us to overlook a problem: if the scheduled task is executed, the task of the next cycle has already arrived , then subsequent tasks may be affected. For example, we define a scheduled task A with an interval of 5 seconds, which starts execution at the 1st second and takes 10 seconds to execute. At the 6th second, scheduled task A has not yet ended. At this time, task B in the next cycle has begun waiting for execution. If there are not enough idle threads in the thread pool at this time, scheduled task B will be blocked and cannot be executed.

    2. The concurrent execution of multiple scheduled tasks may lead to resource competition

    In some cases, we may need to write multiple scheduled tasks, and these scheduled tasks may involve shared resources, such as databases Connections, cache objects, etc. When multiple scheduled tasks are executed at the same time, there will be resource competition issues, which may lead to data errors or system crashes.

    4. @Scheduled joins the thread pool to process scheduled tasks

    In order to avoid the above problems, the @Scheduled task can be handed over to the thread pool for processing. In Spring Boot, you can add @Scheduled tasks to the thread pool in the following two ways:

    1. Use @EnableScheduling @Configuration to configure ThreadPoolTaskScheduler

    @Configuration
    @EnableScheduling
    public class TaskSchedulerConfig {
        @Bean
        public TaskScheduler taskScheduler() {
            ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
            scheduler.setPoolSize(10);
            scheduler.initialize();
            return scheduler;
        }
    }

    In the above code, we configure ThreadPoolTaskScheduler creates a thread pool and uses the @EnableScheduling annotation to enable scheduled tasks. Among them, the setPoolSize method can set the size of the thread pool, which defaults to 1.

    2. Use ThreadPoolTaskExecutor

    @Configuration
    @EnableScheduling
    public class TaskExecutorConfig {
        @Bean
        public ThreadPoolTaskExecutor taskExecutor() {
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            executor.setCorePoolSize(10);
            executor.setMaxPoolSize(50);
            executor.setQueueCapacity(1000);
            executor.setKeepAliveSeconds(60);
            executor.setThreadNamePrefix("task-executor-");
            return executor;
        }
    }

    In the above code, we create a thread pool by configuring ThreadPoolTaskExecutor, and use the @EnableScheduling annotation to turn on the scheduled task. Among them, setCorePoolSize, setMaxPoolSize, setQueueCapacity, setKeepAliveSeconds and other methods can be used to configure parameters such as the size of the thread pool and the task queue.

    5. Detailed analysis of @Scheduled

    In Spring Boot, the @Scheduled annotation is implemented based on Java's ThreadPoolExecutor and ScheduledThreadPoolExecutor. When we configure a scheduled task, Spring Boot will first create a ScheduledThreadPoolExecutor thread pool and add the scheduled task to the thread pool to wait for execution. Then, after the specified time arrives, the thread pool will allocate a thread to execute the scheduled task. If the scheduled task has not been executed yet, when the next cycle arrives, the thread pool will again allocate a thread for execution to the task. In this way, @Scheduled can easily implement periodic scheduled tasks.

    Although the @Scheduled annotation is very convenient, it also has some multi-threading problems, mainly reflected in the following two aspects:

    1. 定时任务未执行完毕时,后续任务可能会受到影响

    在使用@Scheduled注解时,我们很容易忽略一个问题:如果定时任务在执行时,下一个周期的任务已经到了,那么后续任务可能会受到影响。例如,我们定义了一个间隔时间为5秒的定时任务A,在第1秒时开始执行,需要执行10秒钟。在第6秒时,定时任务A还没有结束,此时下一个周期的任务B已经开始等待执行。如果此时线程池中没有足够的空闲线程,那么定时任务B就会被阻塞,无法执行。

    解决方案:

    针对上述问题,我们可以采用以下两种方案来解决:

    方案一:修改线程池大小

    为了避免因为线程池中线程数量不足引起的问题,我们可以对线程池进行配置,提高线程池的大小,从而确保有足够的空闲线程来处理定时任务。

    例如,我们可以在application.properties或application.yml或者使用@EnableScheduling + @Configuration来配置线程池大小:

    spring.task.scheduling.pool.size=20

    2. 多个定时任务并发执行可能导致资源竞争

    在某些情况下,我们可能需要编写多个定时任务,这些定时任务可能涉及到共享资源,例如数据库连接、缓存对象等。当多个定时任务同时执行时,就会存在资源竞争的问题,可能会导致数据错误或者系统崩溃。

    解决方案:

    为了避免由于多个定时任务并发执行导致的资源竞争问题,我们可以采用以下两种方案来解决:

    方案一:使用锁机制

    锁机制是一种常见的解决多线程并发访问共享资源的方式。在Java中,我们可以使用synchronized关键字或者Lock接口来实现锁机制。

    例如,下面是一个使用synchronized关键字实现锁机制的示例:

    private static Object lockObj = new Object();
    
    @Scheduled(fixedDelay = 1000)
    public void doSomething(){
        synchronized(lockObj){
            // 定时任务要执行的内容
        }
    }

    在上述代码中,我们定义了一个静态对象lockObj,用来保护共享资源。在定时任务执行时,我们使用synchronized关键字对lockObj进行加锁,从而确保多个定时任务不能同时访问共享资源。

    方案二:使用分布式锁

    除了使用传统的锁机制外,还可以使用分布式锁来解决资源竞争问题。分布式锁是一种基于分布式系统的锁机制,它可以不依赖于单个JVM实例,从而能够保证多个定时任务之间的资源访问不会冲突。

    在Java开发中,我们可以使用ZooKeeper、Redis等分布式系统来实现分布式锁机制。例如,使用Redis实现分布式锁的示例代码如下:

    @Autowired
    private RedisTemplate redisTemplate;
    
    @Scheduled(fixedDelay = 1000)
    public void doSomething(){
        String lockKey = "lock:key";
        String value = UUID.randomUUID().toString();
        Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, value, 5L, TimeUnit.SECONDS);
        if(result){
            try{
                // 定时任务要执行的内容
            }finally{
                redisTemplate.delete(lockKey);
            }
        }
    }

    在上述代码中,我们使用Redis实现了分布式锁机制。具体而言,我们在定时任务执行时,首先向Redis中写入一个键值对,然后检查是否成功写入。如果成功写入,则表示当前定时任务获得了锁,可以执行接下来的操作。在定时任务执行完毕后,我们再从Redis中删除该键值对,释放锁资源。

    The above is the detailed content of How to use multi-threading of scheduled tasks @Scheduled in SpringBoot. For more information, please follow other related articles on the PHP Chinese website!

    Statement:
    This article is reproduced at:yisu.com. If there is any infringement, please contact admin@php.cn delete