首頁 > 軟體

@Scheduled註解不能同時執行多個定時任務的解決方案

2022-10-02 14:00:08

@Scheduled註解不能同時執行多個定時任務

最近在使用定時任務的時候發現,自己寫的定時任務沒有執行,後來查了上網查了一下,才知道@Scheduled註解的定時任務是單執行緒的,同一時間段內只能執行一個定時任務,其它定時任務不執行。

需要設定@Scheduled多執行緒支援,才能實現同一時間段內,執行多個定時任務。

一般情況下面兩個定時任務只會執行第一個定時任務,第二個定時任務不會執行。

/**
     * 測試定時任務1 每天22:00:00執行
     */
    @Scheduled(cron = "0 0 22 * * ?")
    public void test() {
 
        for (int i = 0; i < 20; i++) {
            try {
                Thread.sleep(1000 * 10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("=======================測試定時任務執行1=======================");
        }
    }
 
    /**
     * 測試定時任務2 每天22:10:00執行
     */
    @Scheduled(cron = "0 10 22 * * ?")
    public void test2() {
 
        for (int i = 0; i < 20; i++) {
            try {
                Thread.sleep(1000 * 10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("=======================測試定時任務執行2=======================");
        }
    }

要解決上訴問題,就需要設定 @Scheduled多執行緒支援,新增一個設定類,程式碼如下:

/**
 * @description: 使@schedule支援多執行緒的設定類
 * @author: David Allen
 * @create: 2020-12-08
 **/
@Configuration
public class ScheduleConfig implements SchedulingConfigurer {
 
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
 
        Method[] methods = Job.class.getMethods();
        int defaultPoolSize = 3;
        int corePoolSize = 0;
 
        if (!CollectionUtils.isEmpty(Arrays.asList(methods))) {
 
            for (Method method : methods) {
 
                Scheduled annotation = method.getAnnotation(Scheduled.class);
 
                if (annotation != null) {
 
                    corePoolSize++;
                }
            }
            if (defaultPoolSize > corePoolSize) {
 
                corePoolSize = defaultPoolSize;
            }
 
            taskRegistrar.setScheduler(Executors.newScheduledThreadPool(corePoolSize));
        }
    }
}

@Scheduled同時執行多個定時任務所導致的並行問題

@Scheduled的執行順序

@Scheduled註解會在預設情況下以單執行緒的方式執行定時任務。

這個“單執行緒”指兩個方面:

  • 如果一個定時任務執行時間大於其任務間隔時間,那麼下一次將會等待上一次執行結束後再繼續執行。
  • 如果多個定時任務在同一時刻執行,任務會依次執行。

那麼這種效果肯定不是我們想要的,為了使@Scheduled效率更高,我們可以通過兩種方法將定時任務變成多執行緒執行:

1、在啟動類中設定TaskScheduler執行緒池大小

@Bean
public TaskScheduler taskScheduler() {
    ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
    taskScheduler.setPoolSize(50);
    return taskScheduler;
}

2、利用Spring提供的@Async註解和@EnableAsync註解

@Component
@EnableAsync
public class TimedTask{
    @Async
    @Scheduled(cron = "0 0/1 * * * ?")//每一分鐘執行一次
    public void taskA() {
        //執行你的業務邏輯
    }
    
    @Async
    @Scheduled(cron = "0 0/1 * * * ?")//每一分鐘執行一次
    public void taskB() {
        //執行你的業務邏輯
    }

通過以上方式,定時任務將會以多執行緒的方式開始執行,減小了程式耦合度,提升執行效率。

@Scheduled同步

定時任務在同一時刻開始執行有兩種情況:

  • 多個任務的間隔時間相同,如都是1分鐘執行一次,那麼每一分鐘,這些任務都會一起執行。
  • 多個任務的間隔時間不同,但有重合的時刻。如一個任務每天零點執行,另一個任務每一分鐘執行,那麼這兩個任務在零點的時刻會一起執行。

由於任務都是非同步的,如果多個任務同時操作同一資源,那麼必然會導致錯誤。

這個時候可以給任務加鎖,保證任務互不干擾,擁有在同一時刻執行的執行緒安全:

@Component
@EnableAsync
public class TimedTask{

    private Object lock = new Object();
    
    @Async
    @Scheduled(cron = "0 0 0 * * ?")//每天零點執行
    public void taskA() {
        synchronized(lock){
               //執行你的業務邏輯
        }
    }
    
    @Async
    @Scheduled(cron = "0 0/1 * * * ?")//每一分鐘執行一次
    public void taskB() {
        synchronized(lock){
               //執行你的業務邏輯
        }
    }

控制定時任務的執行順序

如果對執行順序有要求的定時任務,有如下兩種情況:

1、在某一時刻同時執行

如任務A在零點初始化資料,任務B每分鐘更新資料。那麼在零點,必須是任務A先執行,其次才是B。但加鎖只能保證執行緒安全,不能保證執行順序。在這種情況下,我們可以藉助redis設定獲取鎖的順序,亦或標誌字進行執行順序的判斷。

2、總是同時執行

在任務間隔相同的情況下,一般為業務的解耦,不應操作共用資源,應當放至同一個定時任務中執行。

以上為個人經驗,希望能給大家一個參考,也希望大家多多支援it145.com。


IT145.com E-mail:sddin#qq.com