一、什麼是任務調度
任務調度是指基於給定時間點,給定時間間隔或者給定執行次數自動執行任務,本文會介紹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表達式規則執行。