您好,登錄后才能下訂單哦!
本篇內容主要講解“Java中定時任務的實現方式有哪些”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“Java中定時任務的實現方式有哪些”吧!
先從最原始最簡單的方式來講解。可以先創建一個thread
,然后讓它在while
循環里一直運行著,通過sleep
方法來達到定時任務的效果。
public class Task { public static void main(String[] args) { // run in a second final long timeInterval = 1000; Runnable runnable = new Runnable() { @Override public void run() { while (true) { System.out.println("Hello !!"); try { Thread.sleep(timeInterval); } catch (InterruptedException e) { e.printStackTrace(); } } } }; Thread thread = new Thread(runnable); thread.start(); } }
這種方式簡單直接,但是能夠實現的功能有限,而且需要自己來實現。
目前來看,JDK
自帶的Timer API
算是最古老的定時任務實現方式了。Timer
是一種定時器工具,用來在一個后臺線程計劃執行指定任務。它可以安排任務“執行一次”或者定期“執行多次”。
在實際的開發當中,經常需要一些周期性的操作,比如每5分鐘執行某一操作等。對于這樣的操作最方便、高效的實現方式就是使用java.util.Timer
工具類。
imer類的核心方法如下:
// 在指定延遲時間后執行指定的任務 schedule(TimerTask task,long delay); // 在指定時間執行指定的任務。(只執行一次) schedule(TimerTask task, Date time); // 延遲指定時間(delay)之后,開始以指定的間隔(period)重復執行指定的任務 schedule(TimerTask task,long delay,long period); // 在指定的時間開始按照指定的間隔(period)重復執行指定的任務 schedule(TimerTask task, Date firstTime , long period); // 在指定的時間開始進行重復的固定速率執行任務 scheduleAtFixedRate(TimerTask task,Date firstTime,long period); // 在指定的延遲后開始進行重復的固定速率執行任務 scheduleAtFixedRate(TimerTask task,long delay,long period); // 終止此計時器,丟棄所有當前已安排的任務。 cancal(); // 從此計時器的任務隊列中移除所有已取消的任務。 purge();
下面用幾個示例演示一下核心方法的使用。首先定義一個通用的TimerTask
類,用于定義用執行的任務。
public class DoSomethingTimerTask extends TimerTask { private String taskName; public DoSomethingTimerTask(String taskName) { this.taskName = taskName; } @Override public void run() { System.out.println(new Date() + " : 任務「" + taskName + "」被執行。"); } }
在指定延遲時間后執行一次,這類是比較常見的場景,
比如:當系統初始化某個組件之后,延遲幾秒中,然后進行定時任務的執行。
public class DelayOneDemo { public static void main(String[] args) { Timer timer = new Timer(); timer.schedule(new DoSomethingTimerTask("DelayOneDemo"),1000L); } }
執行上述代碼,延遲一秒之后執行定時任務,并打印結果。其中第二個參數單位為毫秒。
在指定的延遲時間開始執行定時任務,定時任務按照固定的間隔進行執行。比如:延遲2秒執行,固定執行間隔為1秒。
public class PeriodDemo { public static void main(String[] args) { Timer timer = new Timer(); timer.schedule(new DoSomethingTimerTask("PeriodDemo"),2000L,1000L); } }
執行程序,會發現2秒之后開始每隔1秒執行一次。
在指定的延遲時間開始執行定時任務,定時任務按照固定的速率進行執行。
比如:延遲2秒執行,固定速率為1秒。
public class FixedRateDemo { public static void main(String[] args) { Timer timer = new Timer(); timer.scheduleAtFixedRate(new DoSomethingTimerTask("FixedRateDemo"),2000L,1000L); } }
執行程序,會發現2秒之后開始每隔1秒執行一次。
此時,你是否疑惑schedule
與scheduleAtFixedRate
效果一樣,為什么提供兩個方法,它們有什么區別?
在了解schedule
與scheduleAtFixedRate
方法的區別之前,先看看它們的相同點:
任務執行未超時,下次執行時間 = 上次執行開始時間 + period;
任務執行超時,下次執行時間 = 上次執行結束時間;
在任務執行未超時時,它們都是上次執行時間加上間隔時間,來執行下一次任務。而執行超時時,都是立馬執行。
它們的不同點在于側重點不同,schedule
方法側重保持間隔時間的穩定,而scheduleAtFixedRate
方法更加側重于保持執行頻率的穩定。
schedule
方法會因為前一個任務的延遲而導致其后面的定時任務延時。計算公式為scheduledExecutionTime
(第n+1次) = realExecutionTime
(第n次) + periodTime
。
也就是說如果第n次執行task時,由于某種原因這次執行時間過長,執行完后的systemCurrentTime>= scheduledExecutionTime
(第n+1次),則此時不做時隔等待,立即執行第n+1次task。
而接下來的第n+2次task的scheduledExecutionTime
(第n+2次)就隨著變成了realExecutionTime(第n+1次)+periodTime
。這個方法更注重保持間隔時間的穩定。
scheduleAtFixedRate
在反復執行一個task的計劃時,每一次執行這個task
的計劃執行時間在最初就被定下來了,也就是scheduledExecutionTime
(第n次)=firstExecuteTime +n*periodTime
。
如果第n次執行task時,由于某種原因這次執行時間過長,執行完后的systemCurrentTime>= scheduledExecutionTime
(第n+1次),則此時不做period間隔等待,立即執行第n+1次task。
接下來的第n+2次的task的scheduledExecutionTime
(第n+2次)依然還是firstExecuteTime+(n+2)*periodTime
這在第一次執行task就定下來了。說白了,這個方法更注重保持執行頻率的穩定。
如果用一句話來描述任務執行超時之后schedule和scheduleAtFixedRate的區別就是:schedule的策略是錯過了就錯過了,后續按照新的節奏來走;scheduleAtFixedRate
的策略是如果錯過了,就努力追上原來的節奏(制定好的節奏)。
Timer計時器可以定時(指定時間執行任務)、延遲(延遲5秒執行任務)、周期性地執行任務(每隔個1秒執行任務)。但是,Timer存在一些缺陷。首先Timer
對調度的支持是基于絕對時間的,而不是相對時間,所以它對系統時間的改變非常敏感。
其次Timer線程是不會捕獲異常的,如果TimerTask
拋出的了未檢查異常則會導致Timer
線程終止,同時Timer也不會重新恢復線程的執行,它會錯誤的認為整個Timer
線程都會取消。同時,已經被安排單尚未執行的TimerTask
也不會再執行了,新的任務也不能被調度。故如果TimerTask
拋出未檢查的異常,Timer將會產生無法預料的行為。
ScheduledExecutorService
是JAVA 1.5后新增的定時任務接口,它是基于線程池設計的定時任務類,每個調度任務都會分配到線程池中的一個線程去執行。也就是說,任務是并發執行,互不影響。
需要注意:只有當執行調度任務時,ScheduledExecutorService
才會真正啟動一個線程,其余時間ScheduledExecutorService
都是出于輪詢任務的狀態。
ScheduledExecutorService主要有以下4個方法:
ScheduledFuture<?> schedule(Runnable command,long delay, TimeUnit unit); <V> ScheduledFuture<V> schedule(Callable<V> callable,long delay, TimeUnit unit); ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnitunit); ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnitunit);
其中scheduleAtFixedRate
和scheduleWithFixedDelay
在實現定時程序時比較方便,運用的也比較多。
ScheduledExecutorService
中定義的這四個接口方法和Timer中對應的方法幾乎一樣,只不過Timer
的scheduled
方法需要在外部傳入一個TimerTask
的抽象任務。 而ScheduledExecutorService
封裝的更加細致了,傳Runnable
或Callable
內部都會做一層封裝,封裝一個類似TimerTask
的抽象任務類(ScheduledFutureTask)。然后傳入線程池,啟動線程去執行該任務。
scheduleAtFixedRate
方法,按指定頻率周期執行某個任務。定義及參數說明:
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit);
參數對應含義:command
為被執行的線程;initialDelay
為初始化后延時執行時間;period
為兩次開始執行最小間隔時間;unit
為計時單位。
使用實例:
public class ScheduleAtFixedRateDemo implements Runnable{ public static void main(String[] args) { ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); executor.scheduleAtFixedRate( new ScheduleAtFixedRateDemo(), 0, 1000, TimeUnit.MILLISECONDS); } @Override public void run() { System.out.println(new Date() + " : 任務「ScheduleAtFixedRateDemo」被執行。"); try { Thread.sleep(2000L); } catch (InterruptedException e) { e.printStackTrace(); } } }
上面是scheduleAtFixedRate
方法的基本使用方式,但當執行程序時會發現它并不是間隔1秒執行的,而是間隔2秒執行。
這是因為,scheduleAtFixedRate
是以period為間隔來執行任務的,如果任務執行時間小于period
,則上次任務執行完成后會間隔period后再去執行下一次任務;但如果任務執行時間大于period,則上次任務執行完畢后會不間隔的立即開始下次任務。
scheduleWithFixedDelay
方法,按指定頻率間隔執行某個任務。定義及參數說明:
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit);
參數對應含義:command
為被執行的線程;initialDelay
為初始化后延時執行時間;period
為前一次執行結束到下一次執行開始的間隔時間(間隔執行延遲時間);unit為計時單位。
使用實例:
public class ScheduleAtFixedRateDemo implements Runnable{ public static void main(String[] args) { ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); executor.scheduleWithFixedDelay( new ScheduleAtFixedRateDemo(), 0, 1000, TimeUnit.MILLISECONDS); } @Override public void run() { System.out.println(new Date() + " : 任務「ScheduleAtFixedRateDemo」被執行。"); try { Thread.sleep(2000L); } catch (InterruptedException e) { e.printStackTrace(); } } }
上面是scheduleWithFixedDelay
方法的基本使用方式,但當執行程序時會發現它并不是間隔1秒執行的,而是間隔3秒。
這是因為scheduleWithFixedDelay
是不管任務執行多久,都會等上一次任務執行完畢后再延遲delay
后去執行下次任務。
除了JDK自帶的API之外,我們還可以使用開源的框架來實現,比如Quartz
。
Quartz是Job scheduling
(作業調度)領域的一個開源項目,Quartz既可以單獨使用也可以跟spring框架整合使用,在實際開發中一般會使用后者。使用Quartz
可以開發一個或者多個定時任務,每個定時任務可以單獨指定執行的時間,例如每隔1小時執行一次、每個月第一天上午10點執行一次、每個月最后一天下午5點執行一次等。
Quartz通常有三部分組成:調度器(Scheduler
)、任務(JobDetail
)、觸發器(Trigger
,包括SimpleTrigger
和CronTrigger
)。下面以具體的實例進行說明。
要使用Quartz,首先需要在項目的pom文件中引入相應的依賴:
<dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.2</version> </dependency> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz-jobs</artifactId> <version>2.3.2</version> </dependency>
定義執行任務的Job
,這里要實現Quartz提供的Job接口:
public class PrintJob implements Job { @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { System.out.println(new Date() + " : 任務「PrintJob」被執行。"); } }
創建Scheduler
和Trigger
,并執行定時任務:
public class MyScheduler { public static void main(String[] args) throws SchedulerException { // 1、創建調度器Scheduler SchedulerFactory schedulerFactory = new StdSchedulerFactory(); Scheduler scheduler = schedulerFactory.getScheduler(); // 2、創建JobDetail實例,并與PrintJob類綁定(Job執行內容) JobDetail jobDetail = JobBuilder.newJob(PrintJob.class) .withIdentity("job", "group").build(); // 3、構建Trigger實例,每隔1s執行一次 Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger", "triggerGroup") .startNow()//立即生效 .withSchedule(SimpleScheduleBuilder.simpleSchedule() .withIntervalInSeconds(1)//每隔1s執行一次 .repeatForever()).build();//一直執行 //4、Scheduler綁定Job和Trigger,并執行 scheduler.scheduleJob(jobDetail, trigger); System.out.println("--------scheduler start ! ------------"); scheduler.start(); } }
執行程序,可以看到每1秒執行一次定時任務。
在上述代碼中,其中Job
為Quartz
的接口,業務邏輯的實現通過實現該接口來實現。
JobDetail
綁定指定的Job,每次Scheduler
調度執行一個Job的時候,首先會拿到對應的Job,然后創建該Job實例,再去執行Job中的execute()
的內容,任務執行結束后,關聯的Job對象實例會被釋放,且會被JVM GC清除。
Trigger
是Quartz
的觸發器,用于通知Scheduler
何時去執行對應Job。SimpleTrigger
可以實現在一個指定時間段內執行一次作業任務或一個時間段內多次執行作業任務。
CronTrigger
功能非常強大,是基于日歷的作業調度,而SimpleTrigger
是精準指定間隔,所以相比SimpleTrigger
,CroTrigger
更加常用。CroTrigger是基于Cron表達式的。
常見的Cron表達式示例如下:
可以看出,基于Quartz
的CronTrigger
可以實現非常豐富的定時任務場景。
從Spring 3
開始,Spring
自帶了一套定時任務工具Spring-Task
,可以把它看成是一個輕量級的Quartz
,使用起來十分簡單,除Spring
相關的包外不需要額外的包,支持注解和配置文件兩種形式。通常情況下在Spring
體系內,針對簡單的定時任務,可直接使用Spring
提供的功能。
基于XML配置文件的形式就不再介紹了,直接看基于注解形式的實現。
使用起來非常簡單,直接上代碼:
@Component("taskJob") public class TaskJob { @Scheduled(cron = "0 0 3 * * ?") public void job1() { System.out.println("通過cron定義的定時任務"); } @Scheduled(fixedDelay = 1000L) public void job2() { System.out.println("通過fixedDelay定義的定時任務"); } @Scheduled(fixedRate = 1000L) public void job3() { System.out.println("通過fixedRate定義的定時任務"); } }
如果是在Spring Boot
項目中,需要在啟動類上添加@EnableScheduling
來開啟定時任務。
上述代碼中,@Component
用于實例化類,這個與定時任務無關。@Scheduled
指定該方法是基于定時任務進行執行,具體執行的頻次是由cron指定的表達式所決定。關于cron表達式上面CronTrigger
所使用的表達式一致。與cron對照的,Spring還提供了fixedDelay
和fixedRate
兩種形式的定時任務執行。
fixedDelay
和fixedRate
的區別于Timer
中的區別很相似。
fixedRate
有一個時刻表的概念,在任務啟動時,T1、T2、T3就已經排好了執行的時刻,比如1分、2分、3分,當T1的執行時間大于1分鐘時,就會造成T2晚點,當T1執行完時T2立即執行。
fixedDelay
比較簡單,表示上個任務結束,到下個任務開始的時間間隔。無論任務執行花費多少時間,兩個任務間的間隔始終是一致的。
Spring Task
本身不支持持久化,也沒有推出官方的分布式集群模式,只能靠開發者在業務應用中自己手動擴展實現,無法滿足可視化,易配置的需求。
以上定時任務方案都是針對單機的,只能在單個JVM
進程中使用。而現在基本上都是分布式場景,需要一套在分布式環境下高性能、高可用、可擴展的分布式任務調度框架。
首先,Quartz
是可以用于分布式場景的,但需要基于數據庫鎖的形式。簡單來說,quartz
的分布式調度策略是以數據庫為邊界的一種異步策略。各個調度器都遵守一個基于數據庫鎖的操作規則從而保證了操作的唯一性,同時多個節點的異步運行保證了服務的可靠。
因此,Quartz
的分布式方案只解決了任務高可用(減少單點故障)的問題,處理能力瓶頸在數據庫,而且沒有執行層面的任務分片,無法最大化效率,只能依靠shedulex
調度層面做分片,但是調度層做并行分片難以結合實際的運行資源情況做最優的分片。
XXL-JOB
是一個輕量級分布式任務調度平臺。特點是平臺化,易部署,開發迅速、學習簡單、輕量級、易擴展。由調度中心和執行器功能完成定時任務的執行。調度中心負責統一調度,執行器負責接收調度并執行。
針對于中小型項目,此框架運用的比較多。
除此之外,還有Elastic-Job
、Saturn
、SIA-TASK
等。
Elastic-Job
具有高可用的特性,是一個分布式調度解決方案。
Saturn
是唯品會開源的一個分布式任務調度平臺,在Elastic Job
的基礎上進行了改造。
SIA-TASK
是宜信開源的分布式任務調度平臺。
到此,相信大家對“Java中定時任務的實現方式有哪些”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。