自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Java中定時(shí)任務(wù)的6種實(shí)現(xiàn)方式,你知道幾種?

開(kāi)發(fā) 后端
通過(guò)本文梳理了6種定時(shí)任務(wù)的實(shí)現(xiàn),就實(shí)踐場(chǎng)景的運(yùn)用來(lái)說(shuō),目前大多數(shù)系統(tǒng)已經(jīng)脫離了單機(jī)模式。對(duì)于并發(fā)量并不是太高的系統(tǒng),xxl-job或許是一個(gè)不錯(cuò)的選擇。

[[415484]]

本文轉(zhuǎn)載自微信公眾號(hào)「程序新視界」,作者二師兄 。轉(zhuǎn)載本文請(qǐng)聯(lián)系程序新視界公眾號(hào)。

幾乎在所有的項(xiàng)目中,定時(shí)任務(wù)的使用都是不可或缺的,如果使用不當(dāng)甚至?xí)斐少Y損。還記得多年前在做金融系統(tǒng)時(shí),出款業(yè)務(wù)是通過(guò)定時(shí)任務(wù)對(duì)外打款,當(dāng)時(shí)由于銀行接口處理能力有限,外加定時(shí)任務(wù)使用不當(dāng),導(dǎo)致發(fā)出大量重復(fù)出款請(qǐng)求。還好在后面環(huán)節(jié)將交易卡在了系統(tǒng)內(nèi)部,未發(fā)生資損。

所以,系統(tǒng)的學(xué)習(xí)一下定時(shí)任務(wù),是非常有必要的。這篇文章就帶大家整體梳理學(xué)習(xí)一下Java領(lǐng)域中常見(jiàn)的幾種定時(shí)任務(wù)實(shí)現(xiàn)。

線程等待實(shí)現(xiàn)

先從最原始最簡(jiǎn)單的方式來(lái)講解??梢韵葎?chuàng)建一個(gè)thread,然后讓它在while循環(huán)里一直運(yùn)行著,通過(guò)sleep方法來(lái)達(dá)到定時(shí)任務(wù)的效果。

  1. public class Task { 
  2.  
  3.     public static void main(String[] args) { 
  4.         // run in a second 
  5.         final long timeInterval = 1000; 
  6.         Runnable runnable = new Runnable() { 
  7.             @Override 
  8.             public void run() { 
  9.                 while (true) { 
  10.                     System.out.println("Hello !!"); 
  11.                     try { 
  12.                         Thread.sleep(timeInterval); 
  13.                     } catch (InterruptedException e) { 
  14.                         e.printStackTrace(); 
  15.                     } 
  16.                 } 
  17.             } 
  18.         }; 
  19.         Thread thread = new Thread(runnable); 
  20.         thread.start(); 
  21.     } 

這種方式簡(jiǎn)單直接,但是能夠?qū)崿F(xiàn)的功能有限,而且需要自己來(lái)實(shí)現(xiàn)。

JDK自帶Timer實(shí)現(xiàn)

目前來(lái)看,JDK自帶的Timer API算是最古老的定時(shí)任務(wù)實(shí)現(xiàn)方式了。Timer是一種定時(shí)器工具,用來(lái)在一個(gè)后臺(tái)線程計(jì)劃執(zhí)行指定任務(wù)。它可以安排任務(wù)“執(zhí)行一次”或者定期“執(zhí)行多次”。

在實(shí)際的開(kāi)發(fā)當(dāng)中,經(jīng)常需要一些周期性的操作,比如每5分鐘執(zhí)行某一操作等。對(duì)于這樣的操作最方便、高效的實(shí)現(xiàn)方式就是使用java.util.Timer工具類(lèi)。

核心方法

Timer類(lèi)的核心方法如下:

  1. // 在指定延遲時(shí)間后執(zhí)行指定的任務(wù) 
  2. schedule(TimerTask task,long delay); 
  3.  
  4. // 在指定時(shí)間執(zhí)行指定的任務(wù)。(只執(zhí)行一次) 
  5. schedule(TimerTask task, Date time); 
  6.  
  7. // 延遲指定時(shí)間(delay)之后,開(kāi)始以指定的間隔(period)重復(fù)執(zhí)行指定的任務(wù) 
  8. schedule(TimerTask task,long delay,long period); 
  9.  
  10. // 在指定的時(shí)間開(kāi)始按照指定的間隔(period)重復(fù)執(zhí)行指定的任務(wù) 
  11. schedule(TimerTask task, Date firstTime , long period); 
  12.  
  13. // 在指定的時(shí)間開(kāi)始進(jìn)行重復(fù)的固定速率執(zhí)行任務(wù) 
  14. scheduleAtFixedRate(TimerTask task,Date firstTime,long period); 
  15.  
  16. // 在指定的延遲后開(kāi)始進(jìn)行重復(fù)的固定速率執(zhí)行任務(wù) 
  17. scheduleAtFixedRate(TimerTask task,long delay,long period); 
  18.  
  19. // 終止此計(jì)時(shí)器,丟棄所有當(dāng)前已安排的任務(wù)。 
  20. cancal(); 
  21.  
  22. // 從此計(jì)時(shí)器的任務(wù)隊(duì)列中移除所有已取消的任務(wù)。 
  23. purge(); 

使用示例

下面用幾個(gè)示例演示一下核心方法的使用。首先定義一個(gè)通用的TimerTask類(lèi),用于定義用執(zhí)行的任務(wù)。

  1. public class DoSomethingTimerTask extends TimerTask { 
  2.  
  3.     private String taskName; 
  4.  
  5.     public DoSomethingTimerTask(String taskName) { 
  6.         this.taskName = taskName; 
  7.     } 
  8.  
  9.     @Override 
  10.     public void run() { 
  11.         System.out.println(new Date() + " : 任務(wù)「" + taskName + "」被執(zhí)行。"); 
  12.     } 

指定延遲執(zhí)行一次

在指定延遲時(shí)間后執(zhí)行一次,這類(lèi)是比較常見(jiàn)的場(chǎng)景,比如:當(dāng)系統(tǒng)初始化某個(gè)組件之后,延遲幾秒中,然后進(jìn)行定時(shí)任務(wù)的執(zhí)行。

  1. public class DelayOneDemo { 
  2.  
  3.     public static void main(String[] args) { 
  4.         Timer timer = new Timer(); 
  5.         timer.schedule(new DoSomethingTimerTask("DelayOneDemo"),1000L); 
  6.     } 

執(zhí)行上述代碼,延遲一秒之后執(zhí)行定時(shí)任務(wù),并打印結(jié)果。其中第二個(gè)參數(shù)單位為毫秒。

固定間隔執(zhí)行

在指定的延遲時(shí)間開(kāi)始執(zhí)行定時(shí)任務(wù),定時(shí)任務(wù)按照固定的間隔進(jìn)行執(zhí)行。比如:延遲2秒執(zhí)行,固定執(zhí)行間隔為1秒。

  1. public class PeriodDemo { 
  2.  
  3.     public static void main(String[] args) { 
  4.         Timer timer = new Timer(); 
  5.         timer.schedule(new DoSomethingTimerTask("PeriodDemo"),2000L,1000L); 
  6.     } 

執(zhí)行程序,會(huì)發(fā)現(xiàn)2秒之后開(kāi)始每隔1秒執(zhí)行一次。

固定速率執(zhí)行

在指定的延遲時(shí)間開(kāi)始執(zhí)行定時(shí)任務(wù),定時(shí)任務(wù)按照固定的速率進(jìn)行執(zhí)行。比如:延遲2秒執(zhí)行,固定速率為1秒。

  1. public class FixedRateDemo { 
  2.  
  3.     public static void main(String[] args) { 
  4.         Timer timer = new Timer(); 
  5.         timer.scheduleAtFixedRate(new DoSomethingTimerTask("FixedRateDemo"),2000L,1000L); 
  6.     } 

執(zhí)行程序,會(huì)發(fā)現(xiàn)2秒之后開(kāi)始每隔1秒執(zhí)行一次。

此時(shí),你是否疑惑schedule與scheduleAtFixedRate效果一樣,為什么提供兩個(gè)方法,它們有什么區(qū)別?

schedule與scheduleAtFixedRate區(qū)別

在了解schedule與scheduleAtFixedRate方法的區(qū)別之前,先看看它們的相同點(diǎn):

  • 任務(wù)執(zhí)行未超時(shí),下次執(zhí)行時(shí)間 = 上次執(zhí)行開(kāi)始時(shí)間 + period;
  • 任務(wù)執(zhí)行超時(shí),下次執(zhí)行時(shí)間 = 上次執(zhí)行結(jié)束時(shí)間;

在任務(wù)執(zhí)行未超時(shí)時(shí),它們都是上次執(zhí)行時(shí)間加上間隔時(shí)間,來(lái)執(zhí)行下一次任務(wù)。而執(zhí)行超時(shí)時(shí),都是立馬執(zhí)行。

它們的不同點(diǎn)在于側(cè)重點(diǎn)不同,schedule方法側(cè)重保持間隔時(shí)間的穩(wěn)定,而scheduleAtFixedRate方法更加側(cè)重于保持執(zhí)行頻率的穩(wěn)定。

schedule側(cè)重保持間隔時(shí)間的穩(wěn)定

schedule方法會(huì)因?yàn)榍耙粋€(gè)任務(wù)的延遲而導(dǎo)致其后面的定時(shí)任務(wù)延時(shí)。計(jì)算公式為scheduledExecutionTime(第n+1次) = realExecutionTime(第n次) + periodTime。

也就是說(shuō)如果第n次執(zhí)行task時(shí),由于某種原因這次執(zhí)行時(shí)間過(guò)長(zhǎng),執(zhí)行完后的systemCurrentTime>= scheduledExecutionTime(第n+1次),則此時(shí)不做時(shí)隔等待,立即執(zhí)行第n+1次task。

而接下來(lái)的第n+2次task的scheduledExecutionTime(第n+2次)就隨著變成了realExecutionTime(第n+1次)+periodTime。這個(gè)方法更注重保持間隔時(shí)間的穩(wěn)定。

scheduleAtFixedRate保持執(zhí)行頻率的穩(wěn)定

scheduleAtFixedRate在反復(fù)執(zhí)行一個(gè)task的計(jì)劃時(shí),每一次執(zhí)行這個(gè)task的計(jì)劃執(zhí)行時(shí)間在最初就被定下來(lái)了,也就是scheduledExecutionTime(第n次)=firstExecuteTime +n*periodTime。

如果第n次執(zhí)行task時(shí),由于某種原因這次執(zhí)行時(shí)間過(guò)長(zhǎng),執(zhí)行完后的systemCurrentTime>= scheduledExecutionTime(第n+1次),則此時(shí)不做period間隔等待,立即執(zhí)行第n+1次task。

接下來(lái)的第n+2次的task的scheduledExecutionTime(第n+2次)依然還是firstExecuteTime+(n+2)*periodTime這在第一次執(zhí)行task就定下來(lái)了。說(shuō)白了,這個(gè)方法更注重保持執(zhí)行頻率的穩(wěn)定。

如果用一句話來(lái)描述任務(wù)執(zhí)行超時(shí)之后schedule和scheduleAtFixedRate的區(qū)別就是:schedule的策略是錯(cuò)過(guò)了就錯(cuò)過(guò)了,后續(xù)按照新的節(jié)奏來(lái)走;scheduleAtFixedRate的策略是如果錯(cuò)過(guò)了,就努力追上原來(lái)的節(jié)奏(制定好的節(jié)奏)。

Timer的缺陷

Timer計(jì)時(shí)器可以定時(shí)(指定時(shí)間執(zhí)行任務(wù))、延遲(延遲5秒執(zhí)行任務(wù))、周期性地執(zhí)行任務(wù)(每隔個(gè)1秒執(zhí)行任務(wù))。但是,Timer存在一些缺陷。首先Timer對(duì)調(diào)度的支持是基于絕對(duì)時(shí)間的,而不是相對(duì)時(shí)間,所以它對(duì)系統(tǒng)時(shí)間的改變非常敏感。

其次Timer線程是不會(huì)捕獲異常的,如果TimerTask拋出的了未檢查異常則會(huì)導(dǎo)致Timer線程終止,同時(shí)Timer也不會(huì)重新恢復(fù)線程的執(zhí)行,它會(huì)錯(cuò)誤的認(rèn)為整個(gè)Timer線程都會(huì)取消。同時(shí),已經(jīng)被安排單尚未執(zhí)行的TimerTask也不會(huì)再執(zhí)行了,新的任務(wù)也不能被調(diào)度。故如果TimerTask拋出未檢查的異常,Timer將會(huì)產(chǎn)生無(wú)法預(yù)料的行為。

JDK自帶ScheduledExecutorService

ScheduledExecutorService是JAVA 1.5后新增的定時(shí)任務(wù)接口,它是基于線程池設(shè)計(jì)的定時(shí)任務(wù)類(lèi),每個(gè)調(diào)度任務(wù)都會(huì)分配到線程池中的一個(gè)線程去執(zhí)行。也就是說(shuō),任務(wù)是并發(fā)執(zhí)行,互不影響。

需要注意:只有當(dāng)執(zhí)行調(diào)度任務(wù)時(shí),ScheduledExecutorService才會(huì)真正啟動(dòng)一個(gè)線程,其余時(shí)間ScheduledExecutorService都是出于輪詢?nèi)蝿?wù)的狀態(tài)。

ScheduledExecutorService主要有以下4個(gè)方法:

  1. ScheduledFuture<?> schedule(Runnable command,long delay, TimeUnit unit); 
  2. <V> ScheduledFuture<V> schedule(Callable<V> callable,long delay, TimeUnit unit); 
  3. ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnitunit); 
  4. ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnitunit); 

其中scheduleAtFixedRate和scheduleWithFixedDelay在實(shí)現(xiàn)定時(shí)程序時(shí)比較方便,運(yùn)用的也比較多。

ScheduledExecutorService中定義的這四個(gè)接口方法和Timer中對(duì)應(yīng)的方法幾乎一樣,只不過(guò)Timer的scheduled方法需要在外部傳入一個(gè)TimerTask的抽象任務(wù)。而ScheduledExecutorService封裝的更加細(xì)致了,傳Runnable或Callable內(nèi)部都會(huì)做一層封裝,封裝一個(gè)類(lèi)似TimerTask的抽象任務(wù)類(lèi)(ScheduledFutureTask)。然后傳入線程池,啟動(dòng)線程去執(zhí)行該任務(wù)。

scheduleAtFixedRate方法

scheduleAtFixedRate方法,按指定頻率周期執(zhí)行某個(gè)任務(wù)。定義及參數(shù)說(shuō)明:

  1. public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, 
  2.     long initialDelay, 
  3.     long period, 
  4.     TimeUnit unit); 

參數(shù)對(duì)應(yīng)含義:command為被執(zhí)行的線程;initialDelay為初始化后延時(shí)執(zhí)行時(shí)間;period為兩次開(kāi)始執(zhí)行最小間隔時(shí)間;unit為計(jì)時(shí)單位。

使用實(shí)例:

  1. public class ScheduleAtFixedRateDemo implements Runnable{ 
  2.  
  3.     public static void main(String[] args) { 
  4.         ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); 
  5.         executor.scheduleAtFixedRate( 
  6.                 new ScheduleAtFixedRateDemo(), 
  7.                 0, 
  8.                 1000, 
  9.                 TimeUnit.MILLISECONDS); 
  10.     } 
  11.  
  12.     @Override 
  13.     public void run() { 
  14.         System.out.println(new Date() + " : 任務(wù)「ScheduleAtFixedRateDemo」被執(zhí)行。"); 
  15.         try { 
  16.             Thread.sleep(2000L); 
  17.         } catch (InterruptedException e) { 
  18.             e.printStackTrace(); 
  19.         } 
  20.     } 

上面是scheduleAtFixedRate方法的基本使用方式,但當(dāng)執(zhí)行程序時(shí)會(huì)發(fā)現(xiàn)它并不是間隔1秒執(zhí)行的,而是間隔2秒執(zhí)行。

這是因?yàn)椋瑂cheduleAtFixedRate是以period為間隔來(lái)執(zhí)行任務(wù)的,如果任務(wù)執(zhí)行時(shí)間小于period,則上次任務(wù)執(zhí)行完成后會(huì)間隔period后再去執(zhí)行下一次任務(wù);但如果任務(wù)執(zhí)行時(shí)間大于period,則上次任務(wù)執(zhí)行完畢后會(huì)不間隔的立即開(kāi)始下次任務(wù)。

scheduleWithFixedDelay方法

scheduleWithFixedDelay方法,按指定頻率間隔執(zhí)行某個(gè)任務(wù)。定義及參數(shù)說(shuō)明:

  1. public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, 
  2.     long initialDelay, 
  3.     long delay, 
  4.     TimeUnit unit); 

參數(shù)對(duì)應(yīng)含義:command為被執(zhí)行的線程;initialDelay為初始化后延時(shí)執(zhí)行時(shí)間;period為前一次執(zhí)行結(jié)束到下一次執(zhí)行開(kāi)始的間隔時(shí)間(間隔執(zhí)行延遲時(shí)間);unit為計(jì)時(shí)單位。

使用實(shí)例:

  1. public class ScheduleAtFixedRateDemo implements Runnable{ 
  2.  
  3.     public static void main(String[] args) { 
  4.         ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); 
  5.         executor.scheduleWithFixedDelay( 
  6.                 new ScheduleAtFixedRateDemo(), 
  7.                 0, 
  8.                 1000, 
  9.                 TimeUnit.MILLISECONDS); 
  10.     } 
  11.  
  12.     @Override 
  13.     public void run() { 
  14.         System.out.println(new Date() + " : 任務(wù)「ScheduleAtFixedRateDemo」被執(zhí)行。"); 
  15.         try { 
  16.             Thread.sleep(2000L); 
  17.         } catch (InterruptedException e) { 
  18.             e.printStackTrace(); 
  19.         } 
  20.     } 

上面是scheduleWithFixedDelay方法的基本使用方式,但當(dāng)執(zhí)行程序時(shí)會(huì)發(fā)現(xiàn)它并不是間隔1秒執(zhí)行的,而是間隔3秒。

這是因?yàn)閟cheduleWithFixedDelay是不管任務(wù)執(zhí)行多久,都會(huì)等上一次任務(wù)執(zhí)行完畢后再延遲delay后去執(zhí)行下次任務(wù)。

Quartz框架實(shí)現(xiàn)

除了JDK自帶的API之外,我們還可以使用開(kāi)源的框架來(lái)實(shí)現(xiàn),比如Quartz。

Quartz是Job scheduling(作業(yè)調(diào)度)領(lǐng)域的一個(gè)開(kāi)源項(xiàng)目,Quartz既可以單獨(dú)使用也可以跟spring框架整合使用,在實(shí)際開(kāi)發(fā)中一般會(huì)使用后者。使用Quartz可以開(kāi)發(fā)一個(gè)或者多個(gè)定時(shí)任務(wù),每個(gè)定時(shí)任務(wù)可以單獨(dú)指定執(zhí)行的時(shí)間,例如每隔1小時(shí)執(zhí)行一次、每個(gè)月第一天上午10點(diǎn)執(zhí)行一次、每個(gè)月最后一天下午5點(diǎn)執(zhí)行一次等。

Quartz通常有三部分組成:調(diào)度器(Scheduler)、任務(wù)(JobDetail)、觸發(fā)器(Trigger,包括SimpleTrigger和CronTrigger)。下面以具體的實(shí)例進(jìn)行說(shuō)明。

Quartz集成

要使用Quartz,首先需要在項(xiàng)目的pom文件中引入相應(yīng)的依賴:

  1. <dependency> 
  2.     <groupId>org.quartz-scheduler</groupId> 
  3.     <artifactId>quartz</artifactId> 
  4.     <version>2.3.2</version> 
  5. </dependency> 
  6. <dependency> 
  7.     <groupId>org.quartz-scheduler</groupId> 
  8.     <artifactId>quartz-jobs</artifactId> 
  9.     <version>2.3.2</version> 
  10. </dependency> 

 

定義執(zhí)行任務(wù)的Job,這里要實(shí)現(xiàn)Quartz提供的Job接口:

  1. public class PrintJob implements Job { 
  2.     @Override 
  3.     public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { 
  4.         System.out.println(new Date() + " : 任務(wù)「PrintJob」被執(zhí)行。"); 
  5.     } 

創(chuàng)建Scheduler和Trigger,并執(zhí)行定時(shí)任務(wù):

  1. public class MyScheduler { 
  2.  
  3.     public static void main(String[] args) throws SchedulerException { 
  4.         // 1、創(chuàng)建調(diào)度器Scheduler 
  5.         SchedulerFactory schedulerFactory = new StdSchedulerFactory(); 
  6.         Scheduler scheduler = schedulerFactory.getScheduler(); 
  7.         // 2、創(chuàng)建JobDetail實(shí)例,并與PrintJob類(lèi)綁定(Job執(zhí)行內(nèi)容) 
  8.         JobDetail jobDetail = JobBuilder.newJob(PrintJob.class) 
  9.                 .withIdentity("job""group").build(); 
  10.         // 3、構(gòu)建Trigger實(shí)例,每隔1s執(zhí)行一次 
  11.         Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger""triggerGroup"
  12.                 .startNow()//立即生效 
  13.                 .withSchedule(SimpleScheduleBuilder.simpleSchedule() 
  14.                         .withIntervalInSeconds(1)//每隔1s執(zhí)行一次 
  15.                         .repeatForever()).build();//一直執(zhí)行 
  16.  
  17.         //4、Scheduler綁定Job和Trigger,并執(zhí)行 
  18.         scheduler.scheduleJob(jobDetail, trigger); 
  19.         System.out.println("--------scheduler start ! ------------"); 
  20.         scheduler.start(); 
  21.     } 

執(zhí)行程序,可以看到每1秒執(zhí)行一次定時(shí)任務(wù)。

在上述代碼中,其中Job為Quartz的接口,業(yè)務(wù)邏輯的實(shí)現(xiàn)通過(guò)實(shí)現(xiàn)該接口來(lái)實(shí)現(xiàn)。

JobDetail綁定指定的Job,每次Scheduler調(diào)度執(zhí)行一個(gè)Job的時(shí)候,首先會(huì)拿到對(duì)應(yīng)的Job,然后創(chuàng)建該Job實(shí)例,再去執(zhí)行Job中的execute()的內(nèi)容,任務(wù)執(zhí)行結(jié)束后,關(guān)聯(lián)的Job對(duì)象實(shí)例會(huì)被釋放,且會(huì)被JVM GC清除。

Trigger是Quartz的觸發(fā)器,用于通知Scheduler何時(shí)去執(zhí)行對(duì)應(yīng)Job。SimpleTrigger可以實(shí)現(xiàn)在一個(gè)指定時(shí)間段內(nèi)執(zhí)行一次作業(yè)任務(wù)或一個(gè)時(shí)間段內(nèi)多次執(zhí)行作業(yè)任務(wù)。

CronTrigger功能非常強(qiáng)大,是基于日歷的作業(yè)調(diào)度,而SimpleTrigger是精準(zhǔn)指定間隔,所以相比SimpleTrigger,CroTrigger更加常用。CroTrigger是基于Cron表達(dá)式的。

常見(jiàn)的Cron表達(dá)式示例如下:

cron

可以看出,基于Quartz的CronTrigger可以實(shí)現(xiàn)非常豐富的定時(shí)任務(wù)場(chǎng)景。

Spring Task

從Spring 3開(kāi)始,Spring自帶了一套定時(shí)任務(wù)工具Spring-Task,可以把它看成是一個(gè)輕量級(jí)的Quartz,使用起來(lái)十分簡(jiǎn)單,除Spring相關(guān)的包外不需要額外的包,支持注解和配置文件兩種形式。通常情況下在Spring體系內(nèi),針對(duì)簡(jiǎn)單的定時(shí)任務(wù),可直接使用Spring提供的功能。

基于XML配置文件的形式就不再介紹了,直接看基于注解形式的實(shí)現(xiàn)。使用起來(lái)非常簡(jiǎn)單,直接上代碼:

  1. @Component("taskJob"
  2. public class TaskJob { 
  3.  
  4.     @Scheduled(cron = "0 0 3 * * ?"
  5.     public void job1() { 
  6.         System.out.println("通過(guò)cron定義的定時(shí)任務(wù)"); 
  7.     } 
  8.  
  9.     @Scheduled(fixedDelay = 1000L) 
  10.     public void job2() { 
  11.         System.out.println("通過(guò)fixedDelay定義的定時(shí)任務(wù)"); 
  12.     } 
  13.  
  14.     @Scheduled(fixedRate = 1000L) 
  15.     public void job3() { 
  16.         System.out.println("通過(guò)fixedRate定義的定時(shí)任務(wù)"); 
  17.     } 

如果是在Spring Boot項(xiàng)目中,需要在啟動(dòng)類(lèi)上添加@EnableScheduling來(lái)開(kāi)啟定時(shí)任務(wù)。

上述代碼中,@Component用于實(shí)例化類(lèi),這個(gè)與定時(shí)任務(wù)無(wú)關(guān)。@Scheduled指定該方法是基于定時(shí)任務(wù)進(jìn)行執(zhí)行,具體執(zhí)行的頻次是由cron指定的表達(dá)式所決定。關(guān)于cron表達(dá)式上面CronTrigger所使用的表達(dá)式一致。與cron對(duì)照的,Spring還提供了fixedDelay和fixedRate兩種形式的定時(shí)任務(wù)執(zhí)行。

fixedDelay和fixedRate的區(qū)別

fixedDelay和fixedRate的區(qū)別于Timer中的區(qū)別很相似。

fixedRate有一個(gè)時(shí)刻表的概念,在任務(wù)啟動(dòng)時(shí),T1、T2、T3就已經(jīng)排好了執(zhí)行的時(shí)刻,比如1分、2分、3分,當(dāng)T1的執(zhí)行時(shí)間大于1分鐘時(shí),就會(huì)造成T2晚點(diǎn),當(dāng)T1執(zhí)行完時(shí)T2立即執(zhí)行。

fixedDelay比較簡(jiǎn)單,表示上個(gè)任務(wù)結(jié)束,到下個(gè)任務(wù)開(kāi)始的時(shí)間間隔。無(wú)論任務(wù)執(zhí)行花費(fèi)多少時(shí)間,兩個(gè)任務(wù)間的間隔始終是一致的。

Spring Task的缺點(diǎn)

Spring Task 本身不支持持久化,也沒(méi)有推出官方的分布式集群模式,只能靠開(kāi)發(fā)者在業(yè)務(wù)應(yīng)用中自己手動(dòng)擴(kuò)展實(shí)現(xiàn),無(wú)法滿足可視化,易配置的需求。

分布式任務(wù)調(diào)度

以上定時(shí)任務(wù)方案都是針對(duì)單機(jī)的,只能在單個(gè)JVM進(jìn)程中使用。而現(xiàn)在基本上都是分布式場(chǎng)景,需要一套在分布式環(huán)境下高性能、高可用、可擴(kuò)展的分布式任務(wù)調(diào)度框架。

Quartz分布式

首先,Quartz是可以用于分布式場(chǎng)景的,但需要基于數(shù)據(jù)庫(kù)鎖的形式。簡(jiǎn)單來(lái)說(shuō),quartz的分布式調(diào)度策略是以數(shù)據(jù)庫(kù)為邊界的一種異步策略。各個(gè)調(diào)度器都遵守一個(gè)基于數(shù)據(jù)庫(kù)鎖的操作規(guī)則從而保證了操作的唯一性,同時(shí)多個(gè)節(jié)點(diǎn)的異步運(yùn)行保證了服務(wù)的可靠。

因此,Quartz的分布式方案只解決了任務(wù)高可用(減少單點(diǎn)故障)的問(wèn)題,處理能力瓶頸在數(shù)據(jù)庫(kù),而且沒(méi)有執(zhí)行層面的任務(wù)分片,無(wú)法最大化效率,只能依靠shedulex調(diào)度層面做分片,但是調(diào)度層做并行分片難以結(jié)合實(shí)際的運(yùn)行資源情況做最優(yōu)的分片。

輕量級(jí)神器XXL-Job

XXL-JOB是一個(gè)輕量級(jí)分布式任務(wù)調(diào)度平臺(tái)。特點(diǎn)是平臺(tái)化,易部署,開(kāi)發(fā)迅速、學(xué)習(xí)簡(jiǎn)單、輕量級(jí)、易擴(kuò)展。由調(diào)度中心和執(zhí)行器功能完成定時(shí)任務(wù)的執(zhí)行。調(diào)度中心負(fù)責(zé)統(tǒng)一調(diào)度,執(zhí)行器負(fù)責(zé)接收調(diào)度并執(zhí)行。

針對(duì)于中小型項(xiàng)目,此框架運(yùn)用的比較多。

其他框架

除此之外,還有Elastic-Job、Saturn、SIA-TASK等。

Elastic-Job具有高可用的特性,是一個(gè)分布式調(diào)度解決方案。

Saturn是唯品會(huì)開(kāi)源的一個(gè)分布式任務(wù)調(diào)度平臺(tái),在Elastic Job的基礎(chǔ)上進(jìn)行了改造。

SIA-TASK是宜信開(kāi)源的分布式任務(wù)調(diào)度平臺(tái)。

小結(jié)

通過(guò)本文梳理了6種定時(shí)任務(wù)的實(shí)現(xiàn),就實(shí)踐場(chǎng)景的運(yùn)用來(lái)說(shuō),目前大多數(shù)系統(tǒng)已經(jīng)脫離了單機(jī)模式。對(duì)于并發(fā)量并不是太高的系統(tǒng),xxl-job或許是一個(gè)不錯(cuò)的選擇。

 

源碼地址:https://github.com/secbr/java-schedule

 

責(zé)任編輯:武曉燕 來(lái)源: 程序新視界
相關(guān)推薦

2022-03-07 11:20:01

分布式代碼微服務(wù)

2021-06-30 07:19:34

SpringBoot定時(shí)任務(wù)

2025-01-21 10:04:40

Java并發(fā)阻塞隊(duì)列

2024-01-22 08:53:00

策略任務(wù)RocketMQ

2024-01-31 08:38:57

Python定時(shí)任務(wù)函數(shù)

2021-11-22 12:35:40

Python命令定時(shí)任務(wù)

2024-02-26 11:12:33

定時(shí)任務(wù)線程

2024-09-20 05:49:04

SpringBoot后端

2025-01-08 09:55:37

Spring接口數(shù)據(jù)庫(kù)

2024-10-15 16:41:35

2024-05-31 13:07:29

.NET Core定時(shí)任務(wù)編程

2024-05-10 07:44:23

C#進(jìn)程程序

2025-03-12 09:54:02

2021-05-07 16:19:36

異步編程Java線程

2022-05-27 06:57:50

Python循環(huán)方式生成器

2023-10-30 11:53:37

繼承JS父類(lèi)

2022-03-28 07:51:25

分布式定時(shí)任務(wù)

2025-03-26 00:35:25

2023-12-19 08:09:06

Python定時(shí)任務(wù)Cron表達(dá)式

2024-04-24 11:24:43

C#數(shù)據(jù)去重
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)