最近用到定時任務,這裏總結一下java中常用的幾種定時方法。
Java自帶的java.util.Timer類,這個類允許你調度一個java.util.TimerTask任務。使用這種方式可以讓你的程序按照某一個頻度執行,但不能在指定時間運行。一般用的較少。 Spring和QuartZ都支持cron,功能都很強大,Spring的優點是稍微簡單一點,QuartZ的優點是沒有Spring也可使用;
一、Timer
這裏我們看一下例子:
package test;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
/**
* 一個實現TimerTask的類(執行具體任務)
* @author cye
*
*/
public class TimerTest extends TimerTask {
private Timer timer;
public static void main(String[] args) {
TimerTest timerTest = new TimerTest();
timerTest.timer = new Timer();
// 立刻開始執行timerTest任務,只執行一次
// timerTest.timer.schedule(timerTest, new Date());
// 立刻開始執行timerTest任務,執行完本次任務後,隔2秒再執行一次
// timerTest.timer.schedule(timerTest,new Date(),2000);
// 一秒鐘後開始執行timerTest任務,只執行一次
// timerTest.timer.schedule(timerTest,1000);
// 一秒鐘後開始執行timerTest任務,執行完本次任務後,隔2秒再執行一次
// timerTest.timer.schedule(timerTest,1000,2000);
// 立刻開始執行timerTest任務,每隔2秒執行一次
timerTest.timer.scheduleAtFixedRate(timerTest, new Date(), 2000);
// 一秒鐘後開始執行timerTest任務,每隔2秒執行一次
// timerTest.timer.scheduleAtFixedRate(timerTest,1000,2000);
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 結束任務執行,程序終止
timerTest.timer.cancel();
// 結束任務執行,程序並不終止,因爲線程是JVM級別的
// timerTest.cancel();
}
@Override
public void run() {
System.out.println("Task is running!");
}
}
二、Quartz
Quartz 是個開源的作業調度框架,爲在 Java 應用程序中進行作業調度提供了簡單卻強大的機制。Quartz 允許開發人員根據時間間隔(或天)來調度作業。它實現了作業和觸發器的多對多關係,還能把多個作業與不同的觸發器關聯。整合了 Quartz 的應用程序可以重用來自不同事件的作業,還可以爲一個事件組合多個作業。雖然可以通過屬性文件(在屬性文件中可以指定 JDBC 事務的數據源、全局作業和/或觸發器偵聽器、插件、線程池,以及更多)配置 Quartz,但它根本沒有與應用程序服務器的上下文或引用集成在一起。結果就是作業不能訪問 Web 服務器的內部函數;例如,在使用 WebSphere 應用服務器時,由 Quartz 調度的作業並不能影響服務器的動態緩存和數據源。
作業和觸發器
Quartz 調度包的兩個基本單元是作業和觸發器。
調度器:調度器用於將與作業觸發器關聯,一個作業可關聯多個觸發器,這樣每個觸發器被可以觸發的作業執行;一個觸發器可用於控制多個作業,觸發觸發時,全部作業將獲得調度。Quartz的調度器由Scheduler接口體現。
作業 是能夠調度的可執行任務,觸發器 提供了對作業的調度(有SimpleTrigger和CronTrigger兩種類型)。雖然這兩個實體很容易合在一起,但在 Quartz 中將它們分離開來是有原因的,而且也很有益處。通過把要執行的工作與它的調度分開,Quartz 允許在不丟失作業本身或作業的上下文的情況下,修改調度觸發器。而且,任何單個的作業都可以有多個觸發器與其關聯。
- 作業: 首先創建一個實現了org.quartz.Job接口的類,並實現這個接口的唯一一個方法execute(JobExecutionContext arg0) throws JobExecutionException
package com.crystal.springmvc.timer;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
/**
* @author cye
*/
public class SimpleQuartzJob implements Job {
/**
* @param context:作業實例的運行時上下文,它提供了對調度器和觸發器的訪問,這兩者協作來啓動作業以及作業的 JobDetail 對象的執行。
* Quartz 通過把作業的狀態放在 JobDetail 對象中並讓 JobDetail 構造函數啓動一個作業的實例,分離了作業的執行和作業周圍的狀態。
* JobDetail 對象儲存作業的偵聽器、羣組、數據映射、描述以及作業的其他屬性。
*/
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
System.out.println("In SimpleQuartzJob - executing its JOB at " + sdf.format(new Date()) + " by " + context.getTrigger());
}
}
2.Cron 觸發器
CronTrigger 基於 cron 表達式,支持類似日曆的重複間隔
package com.crystal.springmvc.timer;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;
public class SimpleTriggerRunner {
public void task() throws SchedulerException {
//實例化一個 SchedulerFactory,獲得此調度器
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
// 初始化job任務
JobDetail jobDetail = JobBuilder.newJob(SimpleQuartzJob.class)
.withIdentity("jobDetail-s1", "jobDetailGroup-s1").build();
// 初始化觸發器 CronTrigger
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity("simpleTrigger", "triggerGroup-s1")
.startAt(new Date()).withSchedule(CronScheduleBuilder.cronSchedule("0/2 * * * * ?")).build();
Date ft = scheduler.scheduleJob(jobDetail, trigger); // 註冊並進行調度
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
System.out.println(jobDetail.getKey() + " 已被安排執行於: " + sdf.format(ft)
+ ",並且以如下重複規則重複執行: " + trigger.getCronExpression());
scheduler.start(); // 開始執行,start()方法被調用後,計時器就開始工作,計時調度中允許放入N個Job
try { // 主線程等待一分鐘
Thread.sleep(60L * 1000L);
} catch (Exception e) {
}
scheduler.shutdown(true); // 關閉定時調度,定時器不再工作
}
}
Cron 表達式包括以下 7 個字段:
格式: [秒] [分] [小時] [日] [月] [周] [年]
序號 | 說明 | 是否必填 | 允許填寫的值 | 允許的通配符 |
---|---|---|---|---|
1 | 秒 | 是 | 0-59 | , - * / |
2 | 分 | 是 | 0-59 | , - * / |
3 | 小時 | 是 | 0-23 | , - * / |
4 | 日 | 是 | 1-31 | , - * ? / L W |
5 | 月 | 是 | 1-12 or JAN-DEC | , - * / |
6 | 周 | 是 | 1-7 or SUN-SAT | , - * ? / L # |
7 | 年 | 否 | empty 或 1970-2099 | , - * / |
通配符說明:
* 表示所有值. 例如:在分的字段上設置 “*”,表示每一分鐘都會觸發。
? 表示不指定值。使用的場景爲不需要關心當前設置這個字段的值。例如:要在每月的10號觸發一個操作,但不關心是周幾,所以需要周位置的那個字段設置爲”?” 具體設置爲 0 0 0 10 * ?
- 表示區間。例如 在小時上設置 “10-12”,表示 10,11,12點都會觸發。
* , 表示指定多個值,例如在周字段上設置 “MON,WED,FRI” 表示週一,週三和週五觸發
/ 用於遞增觸發。如在秒上面設置”5/15” 表示從5秒開始,每增15秒觸發(5,20,35,50)。在月字段上設置’1/3’所示每月1號開始,每隔三天觸發一次。
L 表示最後的意思。在日字段設置上,表示當月的最後一天(依據當前月份,如果是二月還會依據是否是潤年[leap]), 在周字段上表示星期六,相當於”7”或”SAT”。如果在”L”前加上數字,則表示該數據的最後一個。例如在周字段上設置”6L”這樣的格式,則表示“本月最後一個星期五”
W 表示離指定日期的最近那個工作日(週一至週五). 例如在日字段上設置”15W”,表示離每月15號最近的那個工作日觸發。如果15號正好是週六,則找最近的週五(14號)觸發, 如果15號是周未,則找最近的下週一(16號)觸發.如果15號正好在工作日(週一至週五),則就在該天觸發。如果指定格式爲 “1W”,它則表示每月1號往後最近的工作日觸發。如果1號正是週六,則將在3號下週一觸發。(注,”W”前只能設置具體的數字,不允許區間”-“).
注意: ‘L’和 ‘W’可以一組合使用。如果在日字段上設置”LW”,則表示在本月的最後一個工作日觸發(一般指發工資 )
# 序號(表示每月的第幾個周幾),例如在周字段上設置”6#3”表示在每月的第三個週六.注意如果指定”#5”,正好第五週沒有周六,則不會觸發該配置(用在母親節和父親節再合適不過了)
注意:周字段的設置,若使用英文字母是不區分大小寫的 MON 與mon相同.
Corn表達式在線驗證:http://cron.qqe2.com/