Java中實現(xiàn)定時任務(wù)的幾種方式:
- Timer: java.util.Timer, 一個JDK自帶的處理簡單的定時任務(wù)的工具存哲。
- ScheduledExecutorService: java.util.concurrent.ScheduledExecutorService, JDK中的定時任務(wù)接口,可以將定時任務(wù)與線程池結(jié)合使用。
- Sceduled: org.springframework.scheduling.annotation.Scheduled, Spring框架中基于注解來實現(xiàn)定時任務(wù)處理方式。
- Quartz: 支持分布式調(diào)度任務(wù)的開源框架操漠。
Spring Task 與 Quartz 對比:
- Quartz提供了完整的分布式并發(fā)支持官辈,Spring task的話還是需要自己手動配置線程池以及借助其他分布式工具實現(xiàn)援岩。
- Quartz支持調(diào)度信息持久化稿茉,可以去Quartz官方網(wǎng)站下載建表sql锹锰,然后配置數(shù)據(jù)源等信息實現(xiàn)持久化芥炭, Spring task不支持持久化漓库。
- Spring Task 使用起來非常簡單Quartz還需要寫其他配置相對來說比Spring Task復(fù)雜點。
需求簡單的話可以直接使用 Spring Task 园蝠,比如一個簡單的數(shù)據(jù)對接接口渺蒿。如果會有復(fù)雜的業(yè)務(wù)需求不如一步到位,直接使Quartz彪薛。
一茂装、Timer
Timer的schedule
和scheduleAtFixedRate
在任務(wù)計劃時間和實際開始時間沒有異常的情況下,當(dāng)任務(wù)的執(zhí)行時間超過設(shè)置的執(zhí)行間隔善延,會丟失任務(wù)少态。schedule
不受異常時間的控制。
public class App {
public static void main(String[] args) {
Timer t = new Timer();
TimerTask task = new FooTimeTask("foo");
Calendar c = Calendar.getInstance();
c.set(Calendar.SECOND, c.get(Calendar.SECOND) - 10);
Date planTime = c.getTime();
System.out.println("計劃執(zhí)行時間" + planTime);
System.out.println("實際執(zhí)行時間" + new Date());
t.schedule(task, planTime, 3000);
}
}
class FooTimeTask extends TimerTask {
private String name;
public FooTimeTask(String name) {
this.name = name;
}
@Override
public void run() {
try {
System.out.println("name" + name + ",stime=" + new Date());
Thread.sleep(2000);
// Thread.sleep(4000);
System.out.println("name" + name + ",etime=" + new Date());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class App {
public static void main(String[] args) {
Timer t = new Timer();
TimerTask task = new FooTimeTask("foo");
t.scheduleAtFixedRate(task, new Date(), 3000);
}
}
class FooTimeTask extends TimerTask {
private String name;
public FooTimeTask(String name) {
this.name = name;
}
@Override
public void run() {
try {
System.out.println("name" + name + ",stime=" + new Date());
Thread.sleep(2000);
//Thread.sleep(4000);
System.out.println("name" + name + ",etime=" + new Date());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Timer的
scheduleAtFixedRate
在任務(wù)開始時間不正常情況下與schedule
有區(qū)別:存在時間追趕易遣。
public class App {
public static void main(String[] args) {
Timer t = new Timer();
TimerTask task = new FooTimeTask("foo");
Calendar c = Calendar.getInstance();
c.set(Calendar.SECOND, c.get(Calendar.SECOND) - 10);
Date planTime = c.getTime();
System.out.println("計劃執(zhí)行時間" + planTime);
System.out.println("實際執(zhí)行時間" + new Date());
t.scheduleAtFixedRate(task, planTime, 3000);
}
}
class FooTimeTask extends TimerTask {
private String name;
public FooTimeTask(String name) {
this.name = name;
}
@Override
public void run() {
try {
System.out.println("name" + name + ",stime=" + new Date());
Thread.sleep(2000);
//Thread.sleep(4000);
System.out.println("name" + name + ",etime=" + new Date());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
二彼妻、ScheduledExecutorService
摘抄:java定時任務(wù)之Timer和ScheduledExecutorService
Timer的缺陷:
1、執(zhí)行多個任務(wù)的時候,必須第一個執(zhí)行完后才會執(zhí)行第二個侨歉。
2屋摇、timer是單線程執(zhí)行,因此一個任務(wù)拋異常幽邓,其它任務(wù)也不能執(zhí)行了炮温。
import java.util.Timer;
import java.util.TimerTask;
public class TimerTest {
private static long start;
public static void main(String[] args) throws Exception {
TimerTask task1 = new TimerTask() {
@Override
public void run() {
System.out.println("task1 invoked ! "+(System.currentTimeMillis() - start));
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
TimerTask task2 = new TimerTask() {
@Override
public void run() {
System.out.println("task2 invoked ! "+ (System.currentTimeMillis() - start));
}
};
Timer timer = new Timer();
start = System.currentTimeMillis();
timer.schedule(task1, 1000);
timer.schedule(task2, 3000);
}
}
newScheduledThreadPool:
1、可以多個線程同時執(zhí)行不同的任務(wù)牵舵。
2基跑、因為是多個線程所有,任務(wù)拋出異常策添,不影響其它任務(wù)的執(zhí)行息楔。
scheduleAtFixedRate
: 0s后開始執(zhí)行任務(wù),前一次任務(wù)的開始時間和下次任務(wù)的開始時間中間間隔是5s , 如果前一次任務(wù)執(zhí)行時間大于5s 重斑,那么下次任務(wù)會在隊列中等待兵睛,直到前一次任務(wù)執(zhí)行完后,再執(zhí)行窥浪。
scheduleWithFixedDelay
: 0s后開始執(zhí)行任務(wù)祖很,前一次任務(wù)的結(jié)束時間和后一次任務(wù)的開始時間中間間隔是5s。
public class Scheduled {
static ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
static class Task implements Runnable {
public void run() {
try {
Thread.sleep(2000L);
System.out.println(Thread.currentThread().getName());
} catch (Exception e) {
}
}
}
/**
* command:執(zhí)行線程
* initialDelay:初始化延時
* period:兩次開始執(zhí)行最小間隔時間
* delay:前一次執(zhí)行結(jié)束到下一次執(zhí)行開始的間隔時間(間隔執(zhí)行延遲時間)
* unit:計時單位
*/
public static void main(String[] args) {
scheduledExecutorService.scheduleAtFixedRate(new Task(), 0L, 5000L, TimeUnit.MILLISECONDS);
scheduledExecutorService.scheduleWithFixedDelay(new Task(), 0L, 5000L, TimeUnit.MILLISECONDS);
}
}
三漾脂、QuartZ
可參考:Java定時框架Quartz
- Quartz是功能強大的開源作業(yè)調(diào)度庫,可以集成到最小的獨立應(yīng)用程序到最大的電子商務(wù)程序中假颇。
- Quartz可以通過創(chuàng)建簡單或者復(fù)雜的計劃來執(zhí)行成千上萬的任務(wù)。
- 任何任務(wù)標(biāo)準(zhǔn)的Java組件,都可以執(zhí)行相應(yīng)的編程操作骨稿。
- Quartz Scheduler支持JTA事務(wù)和集群笨鸡。
- Quartz實現(xiàn)了任務(wù)和觸發(fā)器多對多的關(guān)系,可以將多個任務(wù)和不同的觸發(fā)器相關(guān)聯(lián)。
Quartz的核心:
-
JobBuilder
:創(chuàng)建Job和JobDetail坦冠。 -
JobDataMap
:可以包含不限量的(序列化的)數(shù)據(jù)對象形耗,在job實例執(zhí)行的時候,可以使用其中的數(shù)據(jù)辙浑;將job加入到scheduler之前激涤,在構(gòu)建JobDetail時,可以將數(shù)據(jù)放入JobDataMap判呕。 -
Job
:Job接口中只有一個方法execute(JobExecutionContext context)
倦踢;表示要執(zhí)行的具體內(nèi)容。
Job的屬性有兩種: 兩者都是在值為true時表示任務(wù)被持久化或者保留侠草;一個Job可以關(guān)聯(lián)多個Trigger, 一個Trigger只能關(guān)聯(lián)一個Job辱挥。
volatility : 表示任務(wù)是否被持久化到數(shù)據(jù)庫存儲;
durability : 沒有trigger關(guān)聯(lián)的時候任務(wù)是否保留边涕; -
JobDetail
:表示一個具體的可執(zhí)行的調(diào)度程序晤碘,Job是這個可執(zhí)行程調(diào)度程序所要執(zhí)行的內(nèi)容愧哟,另外JobDetail還包含了這個任務(wù)調(diào)度的方案和策略。它本身可能是有狀態(tài)的哼蛆。 -
TriggerBuilder
:創(chuàng)建Trigger蕊梧。 -
Trigger
:代表一個調(diào)度參數(shù)的配置。 -
Scheduler
:代表一個調(diào)度容器腮介,一個調(diào)度容器中可以注冊多個JobDetail和Trigger肥矢。當(dāng)Trigger與JobDetail組合,就可以被Scheduler容器調(diào)度了叠洗。
Quartz設(shè)計成JobDetail + Job的原因在于:
- JobDetail用于定義任務(wù)數(shù)據(jù), 真正的執(zhí)行任務(wù)的邏輯在Job中甘改。
- 因為任務(wù)有可能是并發(fā)執(zhí)行的,如果Scheduler直接使用Job, 會存在對同一個Job實例并發(fā)訪問的問題;通過JobDetail綁定Job的方式 ,Scheduler每次執(zhí)行,都會根據(jù)JobDetail創(chuàng)建一個新的Job實例,可以避免并發(fā)訪問的問題灭抑。
Quartz的持久化:http://www.reibang.com/p/d2f7ad108ab2
3.1 QuartZ入門
//@DisallowConcurrentExecution 禁止并發(fā)執(zhí)行同一個Job定義的多個實例十艾。
//@PersistJobDataAfterExecution 多個Job實例共享JobDataMap;對Trigger中的JobDataMap無效
public class MyJob implements Job {
//可以在JobDetail和Trigger中給Job中的屬性賦值
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void execute(JobExecutionContext jobExecutionContext) {
//JobDetail的使用
JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();
JobDataMap triggerDataMap = jobExecutionContext.getTrigger().getJobDataMap();
JobDataMap mergedJobDataMap = jobExecutionContext.getMergedJobDataMap();
System.out.println("MyJob exe " + new Date() +" || "+ jobDataMap.get("job") +" || " + triggerDataMap.get("trigger")+"||"+mergedJobDataMap.get("com") +"||"+name);
System.out.println("=============");
//1.scheduler每次執(zhí)行都會根據(jù)jobDetail創(chuàng)建一個新的Job實例。避免并發(fā)問題腾节。
// 這樣存在的問題:多個Job無法共享JobDataMap忘嫉。
System.out.println("jobDetail"+System.identityHashCode(jobExecutionContext.getJobDetail()));
System.out.println("job"+System.identityHashCode(jobExecutionContext.getJobInstance()));
jobDataMap.put("count",jobDataMap.getInt("count")+1);
System.out.println("計數(shù):"+jobDataMap.getInt("count"));
//2.默認(rèn)是并發(fā)執(zhí)行調(diào)度內(nèi)的Job;Scheduler不會理會Job中的任務(wù)執(zhí)行時間,嚴(yán)格按照調(diào)度執(zhí)行案腺。
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Test
void quartz() throws Exception {
int count = 0;
JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
.withIdentity("job1","group1") //定義屬性
.usingJobData("job","myJobDetail")
.usingJobData("com","jobcom")
.usingJobData("name","ABO") // 會給job中的屬性自動賦值
.usingJobData("count",count)
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1","trigger1") //定義屬性
.usingJobData("trigger","myTrigger")
.usingJobData("com","triggercom")
.usingJobData("name","ABO-trigger") // 會給job中的屬性自動賦值
.startNow()
.withSchedule( //定義作業(yè)執(zhí)行規(guī)則
SimpleScheduleBuilder.simpleSchedule() //簡單的調(diào)度
.withIntervalInSeconds(1) //間隔時間
.repeatForever() //一直執(zhí)行
).build();
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.scheduleJob(jobDetail,trigger);
//默認(rèn)是并發(fā)執(zhí)行調(diào)度內(nèi)的Job;不會理會Job中的任務(wù)執(zhí)行時間庆冕,嚴(yán)格按照調(diào)度執(zhí)行。
scheduler.start();
//測試使用
Thread.sleep(10000);
}
3.2 Trigger
通用的觸發(fā)器包括兩類:
- SimpleTrigger: 根據(jù)Quartz框架的類中自定義的方法設(shè)置定時執(zhí)行規(guī)則劈榨。
- CronTrigger: 基于Cron表達式實現(xiàn)觸發(fā)器访递。
misfire:任務(wù)錯過執(zhí)行的處理。
優(yōu)先級:同時觸發(fā)的Trigger之間優(yōu)先級高的先執(zhí)行同辣。
calendar:用于排除時間段拷姿。
misfire
錯過觸發(fā)的判斷條件:
- job到達觸發(fā)時間時候沒有執(zhí)行。
- 被執(zhí)行的延遲時間超過了Quartz配置的misfireThreshold閾值旱函。
錯過觸發(fā)的產(chǎn)生原因:
- 當(dāng)job達到觸發(fā)時間時响巢,所有線程都被其他job占用了。
- 當(dāng)job需要觸發(fā)的時間點陡舅,scheduler停止了抵乓。
- job使用了@PersistJobDataAfterExecution伴挚。job不能并發(fā)執(zhí)行靶衍,當(dāng)?shù)竭_下一個job執(zhí)行點的時候,上一個任務(wù)還沒有完成茎芋。
- job指定了過去的開始執(zhí)行時間(在閾值范圍內(nèi)颅眶,還可以被正常執(zhí)行)。
錯過觸發(fā)的應(yīng)對策略:
-
SimpleTrigger
:
nowXXX相關(guān)的策略田弥,會立即執(zhí)行第一個misfire的任務(wù)涛酗,同時會修改startTime和repeatCount,因此finalFireTime會重新計算,原計劃執(zhí)行時間會被打亂。
nextXXX相關(guān)的策略商叹,不會立即執(zhí)行misfire的任務(wù)燕刻,也不會修改startTime和repeatCount,finalFireTime也沒有改變剖笙,發(fā)生了misfire也還是按照原計劃進行執(zhí)行卵洗。 -
CronTrigger
:
MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY:Quartz不會判斷發(fā)生了misfire,立即執(zhí)行所有發(fā)生了misfire的任務(wù),然后按照原計劃進行執(zhí)行弥咪。
MISFIRE_INSTRUCTION_FIRE_ONCE_NOW:立即執(zhí)行第一個發(fā)生misfire的任務(wù)过蹂,忽略其他發(fā)生misfire的任務(wù),按照原計劃繼續(xù)執(zhí)行聚至。
MISFIRE_INSTRUCTION_DO_NOTHING:所有發(fā)生misfire的任務(wù)都被忽略酷勺,只是按照計劃繼續(xù)執(zhí)行。
默認(rèn)使用MISFIRE_INSTRUCTION_SMART_POLICY
策略扳躬。
3.3 scheduler
SchedulerFactory:
- 創(chuàng)建Scheduler脆诉。
- DireSchedulerFactory:在代碼中定制Scheduler參數(shù)。
- StdSchedulerFactory:讀取類路徑下quartz.properties配置實例化Scheduler贷币。
JobStore:
- 存儲運行時的信息库说,包括:Trigger、Scheduler片择、JobDetail潜的、業(yè)務(wù)鎖等。
- RAMJobStore:內(nèi)存實現(xiàn)字管。
- JobStoreTX:JDBC啰挪,事務(wù)由Quartz管理。
- JobStoreCMT:JDBC嘲叔,使用容器事務(wù)亡呵。
- ClusteredJobStore:集群實現(xiàn)。
- TerracottaJobStore:Terracotta中間件硫戈。
ThreadPool:
- SimpleThreadPool锰什。
- 自定義線程池。
3.4 SpringBoot整合Quartz
1.job類
public class QuartzDemo implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println("Execute..."+new Date());
}
}
2.配置類丁逝,配置觸發(fā)器與任務(wù)調(diào)度器
/**
* Quartz配置類
*/
@Configuration
public class QuartzConfig {
/**
* 1汁胆、創(chuàng)建Job對象
*/
@Bean
public JobDetailFactoryBean jobDetailFactoryBean(){
JobDetailFactoryBean factoryBean=new JobDetailFactoryBean();
//關(guān)聯(lián)我們自己的Job類
factoryBean.setJobClass(QuartzDemo.class);
return factoryBean;
}
/**
* 2、創(chuàng)建Trigger對象
*/
@Bean
public SimpleTriggerFactoryBean simpleTriggerFactoryBean(JobDetailFactoryBean jobDetailFactoryBean){
SimpleTriggerFactoryBean factoryBean=new SimpleTriggerFactoryBean();
//關(guān)聯(lián)JobDetail對象
factoryBean.setJobDetail(jobDetailFactoryBean.getObject());
//該參數(shù)表示一個執(zhí)行的毫秒數(shù)
factoryBean.setRepeatInterval(2000); //每隔2秒執(zhí)行一次
//重復(fù)次數(shù)
factoryBean.setRepeatCount(5);
return factoryBean;
}
/**
* 3霜幼、創(chuàng)建Scheduler
*/
@Bean
public SchedulerFactoryBean schedulerFactoryBean(SimpleTriggerFactoryBean simpleTriggerFactoryBean){
SchedulerFactoryBean factoryBean=new SchedulerFactoryBean();
//關(guān)聯(lián)trigger
factoryBean.setTriggers(simpleTriggerFactoryBean.getObject());
return factoryBean;
}
}
3.啟動類
@SpringBootApplication
@EnableScheduling
public class QuartzApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(QuartzApplication.class, args);
}
}