任務調(diào)度是指基于給定的時間點,給定的時間間隔或者給定執(zhí)行次數(shù)自動得執(zhí)行任務。任務調(diào)度是是操作系統(tǒng)的重要組成部分,而對于實時的操作系統(tǒng),任務調(diào)度直接影響著操作系統(tǒng)的實時性能。
 我們在實際開發(fā)中,多多少少都會用到定時任務來處理一些問題。
比如金融項目中的對賬,每天定時對昨天的賬務進行核對,每個月初對上個月的賬務進行核對等。
還比如,我們需要處理一些老數(shù)據(jù)遷移,修復一些新項目和老項目數(shù)據(jù)不兼容的問題等等。
常規(guī)實現(xiàn)方案
方案1:Timer
這個目前在項目中用得較少,直接貼demo代碼。
具體的介紹可以查看api ,但是在某些框架中是有用到。
public class TestTimer {
    public static void main(String[] args){
        TimerTask timerTask = new TimerTask() {
            @Override
            public void run(){
                System.out.println("task  run:"+ new Date());
            }
        };
        Timer timer = new Timer();
        //安排指定的任務在指定的時間開始進行重復的固定延遲執(zhí)行。這里是每3秒執(zhí)行一次
        timer.schedule(timerTask,10,3000);
    }
}執(zhí)行結(jié)果:
task  run:Sun Dec 11 21:23:47 CST 2022
task  run:Sun Dec 11 21:23:50 CST 2022
task  run:Sun Dec 11 21:23:53 CST 2022
這么使用,阿里代碼檢查插件會提示:

從提示中可以看出,在多線程并行處理定時任務時,Timer運行多個TimerTask時,只要有其中之一沒有捕獲拋出的異常,其他任務會自動終止運行。
方案2:ScheduledExecutorService
和Timer類型,也就是阿里代碼檢查插件推薦的方案:
public class TestScheduledExecutorService {
    public static void main(String[] args){
        ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
        // 參數(shù):1、任務體 2、首次執(zhí)行的延時時間
        // 3、任務執(zhí)行間隔 4、間隔時間單位
        service.scheduleAtFixedRate(()->System.out.println("task ScheduledExecutorService "+new Date()), 0, 3, TimeUnit.SECONDS);
    }
}運行結(jié)果:
task ScheduledExecutorService Sun Dec 11 21:30:06 CST 2022
task ScheduledExecutorService Sun Dec 11 21:30:09 CST 2022
task ScheduledExecutorService Sun Dec 11 21:30:12 CST 2022
阿里代碼檢查插件也會提示:

這里提示的是我們創(chuàng)建線程池的方式,建議我們使用手動創(chuàng)建線程池,不要使用??Executors??工廠類,因為手動創(chuàng)建更能有效規(guī)劃資源的使用。
方案3:spring task
用起來也非常簡單:
@Slf4j
@Component
public class ScheduledService {
    @Scheduled(cron = "0/5 * * * * *")
    public void scheduled(){
        log.info("=====>>>>>使用cron  {}",System.currentTimeMillis());
    }
    @Scheduled(fixedRate = 5000)
    public void scheduled1(){
        log.info("=====>>>>>使用fixedRate{}", System.currentTimeMillis());
    }
    @Scheduled(fixedDelay = 5000)
    public void scheduled2(){
        log.info("=====>>>>>fixedDelay{}",System.currentTimeMillis());
    }
}
運行結(jié)果:
2022-12-11 21:36:25.001  INFO 10660 --- [   scheduling-1] com.tian.utils.ScheduledService          : =====>>>>>使用cron  1670765785001
2022-12-11 21:36:28.212  INFO 10660 --- [   scheduling-1] com.tian.utils.ScheduledService          : =====>>>>>使用fixedRate1670765788212
2022-12-11 21:36:28.212  INFO 10660 --- [   scheduling-1] com.tian.utils.ScheduledService          : =====>>>>>fixedDelay1670765788212
2022-12-11 21:36:30.001  INFO 10660 --- [   scheduling-1] com.tian.utils.ScheduledService          : =====>>>>>使用cron  1670765790001
2022-12-11 21:36:33.212  INFO 10660 --- [   scheduling-1] com.tian.utils.ScheduledService          : =====>>>>>使用fixedRate1670765793212
2022-12-11 21:36:33.213  INFO 10660 --- [   scheduling-1] com.tian.utils.ScheduledService          : =====>>>>>fixedDelay1670765793213
2022-12-11 21:36:35.001  INFO 10660 --- [   scheduling-1] com.tian.utils.ScheduledService          : =====>>>>>使用cron  1670765795001
2022-12-11 21:36:38.214  INFO 10660 --- [   scheduling-1] com.tian.utils.ScheduledService          : =====>>>>>使用fixedRate1670765798214
2022-12-11 21:36:38.214  INFO 10660 --- [   scheduling-1] com.tian.utils.ScheduledService          : =====>>>>>fixedDelay1670765798214
2022-12-11 21:36:40.001  INFO 10660 --- [   scheduling-1] com.tian.utils.ScheduledService          : =====>>>>>使用cron  1670765800001
2022-12-11 21:36:43.214  INFO 10660 --- [   scheduling-1] com.tian.utils.ScheduledService          : =====>>>>>使用fixedRate1670765803214
2022-12-11 21:36:43.215  INFO 10660 --- [   scheduling-1] com.tian.utils.ScheduledService          : =====>>>>>fixedDelay1670765803215
方案4:多線程執(zhí)行
基于注解設定多線程定時任務 :
@Component
@EnableScheduling   // 1.開啟定時任務
@EnableAsync        // 2.開啟多線程
public class MultithreadScheduleTask {
    @Async
    @Scheduled(fixedDelay = 5000)  //間隔5秒
    public void first() throws InterruptedException {
        System.out.println("第一個定時任務開始 : " + LocalDateTime.now().toLocalTime() + "\r\n線程 : " + Thread.currentThread().getName());
        System.out.println();
        Thread.sleep(1000 * 10);
    }
    @Async
    @Scheduled(fixedDelay = 5000)
    public void second(){
        System.out.println("第二個定時任務開始 : " + LocalDateTime.now().toLocalTime() + "\r\n線程 : " + Thread.currentThread().getName());
        System.out.println();
    }
}
運行結(jié)果:
第一個定時任務開始 : 21:44:02.800
線程 : 入庫操作日志記錄表 線程1
第二個定時任務開始 : 21:44:02.801
線程 : 入庫操作日志記錄表 線程2
第一個定時任務開始 : 21:44:07.801
線程 : 入庫操作日志記錄表 線程3
第二個定時任務開始 : 21:44:07.802
線程 : 入庫操作日志記錄表 線程4
第一個定時任務開始 : 21:44:12.807
線程 : 入庫操作日志記錄表 線程5
第二個定時任務開始 : 21:44:12.812
線程 : 入庫操作日志記錄表 線程6
......
方案5:quartz
我們需要引入依賴:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
實現(xiàn)類:
public class Myquartz extends QuartzJobBean {
    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        System.out.println("這是我的 quartz 定時任務");
    }
}配置類:
/**
 * @author tianwc  公眾號:java后端技術(shù)全棧、面試專欄
 * @version 1.0.0
 * @date 2022年12月11日 21:48
 */
@Configuration
public class QuartzConfig {
    @Bean
    public JobDetail teatQuartzDetail(){
        return JobBuilder.newJob(MyQuartz.class).withIdentity("myQuartz").storeDurably().build();
    }
    @Bean
    public Trigger testQuartzTrigger(){
        SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
                .withIntervalInSeconds(10)  //設置時間周期單位秒
                .repeatForever();
        return TriggerBuilder.newTrigger().forJob(teatQuartzDetail())
                .withIdentity("testQuartz")
                .withSchedule(scheduleBuilder)
                .build();
    }
}
只要啟動Spring Boot項目,就會輸出:
這是我的 quartz 定時任務
這是我的 quartz 定時任務
這是我的 quartz 定時任務
其他方案
我們在項目,可能會涉及動態(tài)調(diào)整定時任務執(zhí)行core表達式、動態(tài)關(guān)閉開啟定時任務,我們可以使用??SchedulingConfigurer??來實現(xiàn)(使用數(shù)據(jù)庫結(jié)合來搞):
比如:
@Configuration
public class ScheduledConfig implements SchedulingConfigurer {
    @Autowired
    private ApplicationContext context; 
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar){
        for (SpringScheduledCron springScheduledCron : cronRepository.findAll()) {
            Class<?> clazz;
            Object task;
            try {
                clazz = Class.forName(springScheduledCron.getCronKey());
                task = context.getBean(clazz);
            } catch (ClassNotFoundException e) {
                throw new IllegalArgumentException("spring_scheduled_cron表數(shù)據(jù)" + springScheduledCron.getCronKey() + "有誤", e);
            } catch (BeansException e) {
                throw new IllegalArgumentException(springScheduledCron.getCronKey() + "未納入到spring管理", e);
            }
            Assert.isAssignable(ScheduledOfTask.class, task.getClass(), "定時任務類必須實現(xiàn)ScheduledOfTask接口");
            // 可以通過改變數(shù)據(jù)庫數(shù)據(jù)進而實現(xiàn)動態(tài)改變執(zhí)行周期
            taskRegistrar.addTriggerTask(((Runnable) task),
                    triggerContext -> {
                        //這個可以使用持久層,比如Mybatis來實現(xiàn),從數(shù)據(jù)庫中獲取
                        String cronExpression = "0/10 * * * * ? "                       
                        return new CronTrigger(cronExpression).nextExecutionTime(triggerContext);
                    }
            );
        }
    }
    @Bean
    public Executor taskExecutor(){
        return Executors.newScheduledThreadPool(10);
    }
}
如果項目中用得到類似的,可以網(wǎng)上搜搜SchedulingConfigurer便可實現(xiàn)。
進而再擴展,那就來到分布式任務調(diào)度了。
什么是分布式任務調(diào)度?
任務調(diào)度是指基于給定的時間點,給定的時間間隔或者給定執(zhí)行次數(shù)自動得執(zhí)行任務。任務調(diào)度是是操作系統(tǒng)的重要組成部分,而對于實時的操作系統(tǒng),任務調(diào)度直接影響著操作系統(tǒng)的實時性能。任務調(diào)度涉及到多線程并發(fā)、運行時間規(guī)則定制及解析、線程池的維護等諸多方面的工作。
WEB服務器在接受請求時,會創(chuàng)建一個新的線程服務。但是資源有限,必須對資源進行控制,首先就是限制服務線程的最大數(shù)目,其次考慮以線程池共享服務的線程資源,降低頻繁創(chuàng)建、銷毀線程的消耗;然后任務調(diào)度信息的存儲包括運行次數(shù)、調(diào)度規(guī)則以及運行數(shù)據(jù)等。一個合適的任務調(diào)度框架對于項目的整體性能來說顯得尤為重要。
分布式任務調(diào)度框架有:cronsun、Elastic-job、saturn、lts、TBSchedule、xxl-job 等。
另外,就是cron表達式,推薦 http://www.pppet.net/

可以根據(jù)自己業(yè)務情況來,手動選擇,自動生成表達式。