談談任務調度的四種實現方式

一、什麼是任務調度

任務調度是指基於給定時間點,給定時間間隔或者給定執行次數自動執行任務,本文會介紹Timer、ScheduledExecutor、Quartz、Spring Boot中的調度模塊使用。

二、Timer

Timer任務調度的核心類是 Timer 和 TimerTask。其中Timer負責設定TimerTask的起始與間隔執行時間。使用者只需要創建一個 TimerTask 的繼承類,實現自己的run方法,然後將其丟給 Timer 去執行即可。

下面的例子每隔半秒執行run中的方法,輸出當前時間和name。

public class Main {
    public static void main(String[] args) throws ClassNotFoundException {
        Timer timer = new Timer();
        timer.schedule(new TestTimerTask("test"), 0, 500);
    }
    static class TestTimerTask extends TimerTask {
        private String name;
        public TestTimerTask(String name) {
            this.name = name;
        }
        @Override
        public void run() {
            System.out.println(LocalDateTime.now()+"-----run----" + name);
        }
    }
}

運行結果:

2020-05-19T09:19:11.301-----run----test
2020-05-19T09:19:11.741-----run----test
2020-05-19T09:19:12.241-----run----test
2020-05-19T09:19:12.742-----run----test
2020-05-19T09:19:13.242-----run----test
2020-05-19T09:19:13.742-----run----test
2020-05-19T09:19:14.242-----run----test
2020-05-19T09:19:14.743-----run----test
2020-05-19T09:19:15.243-----run----test
2020-05-19T09:19:15.743-----run----test
2020-05-19T09:19:16.244-----run----test
2020-05-19T09:19:16.743-----run----test
2020-05-19T09:19:17.243-----run----test

因爲Timer底層是使用一個單線來實現多個Timer任務處理的,所有任務都是由同一個線程來調度,所有任務都是串行執行,意味着同一時間只能有一個任務得到執行,而某一個任務的延遲或者異常會影響到之後的任務。如下面這段,當test2拋出異常後,test也會終止。

public class Main {
    public static void main(String[] args) throws ClassNotFoundException {
        Timer timer = new Timer();
        timer.schedule(new TestTimerTask("test"), 0, 500);
        timer.schedule(new TestTimerTask("test2"), 0, 500);
    }
    static class TestTimerTask extends TimerTask {
        private String name;
        private int count ;
        public TestTimerTask(String name) {
            this.name = name;
        }
        @Override
        public void run() {
            if ("test2".equals(name)){
                count++;
                if (count>5){
                    throw  new NullPointerException();
                }
            }
            System.out.println(LocalDateTime.now()+"-----run----" + name);
        }
    }
}

三、ScheduledExecutor

鑑於 Timer 的上述缺陷,Java 5 推出了基於線程池設計的 ScheduledExecutor。其設計思想是,每一個被調度的任務都會由線程池中一個線程去執行,因此任務是併發執行的,相互之間不會受到干擾。需要注意的是,只有當任務的執行時間到來時,ScheduedExecutor 纔會真正啓動一個線程,其餘時間 ScheduledExecutor 都是在輪詢任務的狀態。

下面這段是每隔一秒執行一次任務。

public class Main {
    public static void main(String[] args) throws ClassNotFoundException {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
        scheduledExecutorService.scheduleWithFixedDelay(new TestRunnable("test1"),0,1, TimeUnit.SECONDS);
    }
    static class TestRunnable  implements Runnable{
        private String name;
        public TestRunnable(String name) {
            this.name = name;
        }
        @Override
        public void run() {
            System.out.println(LocalDateTime.now()+"-----run----" + name);
        }
    }
}

運行結果:

2020-05-19T09:30:07.005-----run----test1
2020-05-19T09:30:08.005-----run----test1
2020-05-19T09:30:09.006-----run----test1
2020-05-19T09:30:10.006-----run----test1

ScheduledExecutorService中常用的調度方法是scheduleAtFixedRate和scheduleWithFixedDelay。

scheduleAtFixedRate每次執行時間爲上一次任務開始起向後推一個時間間隔,即每次執行時間爲 :initialDelay, initialDelay+period, initialDelay+2*period, …。

scheduleWithFixedDelay每次執行時間爲上一次任務結束起向後推一個時間間隔,即每次執行時間爲:initialDelay, initialDelay+executeTime+delay, initialDelay+2executeTime+2delay。由此可見,scheduleAtFixedRate是基於固定時間間隔進行任務調度,ScheduleWithFixedDelay取決於每次任務執行的時間長短,是基於不固定時間間隔進行任務調度。

四、Quartz

Quartz是一個由OpenSymphony組織開源的java調度框架。特點如下。

(1)具有強大的調度功能,很容易與spring集成,形成靈活可配置的調度功能。
(2)調度環境的持久化機制:可以保存並恢復調度現場,即使系統因爲故障關閉,任務調度現場的數據並不會丟失。
(3)靈活的應用方式:可以靈活的定義觸發器調度的時間表,並可以對觸發器與任務進行關聯映射;
(4)分佈式與集羣能力。

 <dependency>
     <groupId>org.quartz-scheduler</groupId>
     <artifactId>quartz</artifactId>
     <version>2.3.0</version>
 </dependency>

下面這段還是每隔1秒打印當前時間。

public class Main   {
    public static void main(String[] args) throws SchedulerException {
        JobDetail jobDetail =JobBuilder.newJob(SimpleQuartzJob.class)
                .withIdentity("testJob","group").build();
        SimpleTrigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("testTrigger", "group")
                .startNow()
                .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                        .withIntervalInSeconds(1).
                                repeatForever()).build();
 
        StdSchedulerFactory stdSchedulerFactory = new StdSchedulerFactory();
        Scheduler scheduler = stdSchedulerFactory.getScheduler();
        scheduler.start();
        scheduler.scheduleJob(jobDetail,trigger);
    }
}

public class SimpleQuartzJob implements Job {
    public SimpleQuartzJob() {}
    public void execute(JobExecutionContext context) throws JobExecutionException {
        System.out.println(LocalDateTime.now()+"   "+context.getJobDetail().getKey().getName());
    }
}

Quartz 設計的核心類包括 Scheduler, Job 以及 Trigger。其中,Job 負責定義需要執行的任務,Trigger 負責設置調度策略,Scheduler 將二者組裝在一起,並觸發任務開始執行。

另外Quartz也支持cron表達式,如下,*/1 * * * * ?爲每隔1秒執行一次。

public class Main   {
    public static void main(String[] args) throws SchedulerException {
        JobDetail jobDetail =JobBuilder.newJob(SimpleQuartzJob.class)
                .withIdentity("testJob","group").build();
//        SimpleTrigger trigger = TriggerBuilder.newTrigger()
//                .withIdentity("testTrigger", "group")
//                .startNow()
//                .withSchedule(SimpleScheduleBuilder.simpleSchedule()
//                        .withIntervalInSeconds(1).
//                                repeatForever()).build();
        CronTrigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("testTrigger", "group")
                .startNow()
                .withSchedule(CronScheduleBuilder.cronSchedule("*/1 * * * * ?"))
                .build();
        StdSchedulerFactory stdSchedulerFactory = new StdSchedulerFactory();
        Scheduler scheduler = stdSchedulerFactory.getScheduler();
        scheduler.start();
        scheduler.scheduleJob(jobDetail,trigger);
    }
}

五、Spring Boot中任務調度

在Spring Boot中開發定時任務需要在啓動類上增加一個@EnableScheduling註解來開啓定時任務功能。
在這裏插入圖片描述
其次,使用@Component註解標註一個類,在其需要定時執行的方法上添加@Scheduled註解。

@Component
public class TestScheduled {
    @Scheduled(fixedDelay = 3000)
    public  void test1(){
        System.out.println(LocalDateTime.now()+"  fixedDelay");
    }
    @Scheduled(fixedRate = 5000)
    public  void test2(){
        System.out.println(LocalDateTime.now()+"  fixedRate");
    }
    @Scheduled(cron = "*/1 * * * * ?")
    public  void test3(){
        System.out.println(LocalDateTime.now()+"  cron");
    }
}
@Scheduled中的參數如下:

	@Scheduled(fixedDelay = 3000):上次執行完畢後時間後3秒再次執行。
	@Scheduled(fixedRate = 5000):上次開始執行時間點後5秒再次執行。
	@Scheduled(cron = "*/1 * * * * ?"):按照cron表達式規則執行。
  
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章