在 Java 開發(fā)領(lǐng)域,定時(shí)任務(wù)是一項(xiàng)非常常見的需求,比如定期數(shù)據(jù)備份、定時(shí)發(fā)送通知、周期性數(shù)據(jù)統(tǒng)計(jì)等。實(shí)現(xiàn)定時(shí)任務(wù)的方式有多種,其中 Timer、ScheduledExecutor 和 Quartz 是比較具有代表性的三種。它們各自有著不同的設(shè)計(jì)理念、實(shí)現(xiàn)機(jī)制和適用場(chǎng)景,深入了解它們的特點(diǎn),對(duì)于開發(fā)者在實(shí)際項(xiàng)目中選擇合適的定時(shí)任務(wù)解決方案至關(guān)重要。 一、Timer:Java 早期的定時(shí)任務(wù)解決方案 Timer 是 Java 1.3 版本就引入的定時(shí)任務(wù)工具類,位于 java.util 包下,它的設(shè)計(jì)相對(duì)簡(jiǎn)單,主要依賴 TimerTask 和單線程來實(shí)現(xiàn)定時(shí)任務(wù)調(diào)度。 1. 核心原理 Timer 內(nèi)部維護(hù)了一個(gè)有序的任務(wù)隊(duì)列,這個(gè)隊(duì)列按照任務(wù)的執(zhí)行時(shí)間進(jìn)行排序。同時(shí),Timer 會(huì)啟動(dòng)一個(gè)單獨(dú)的線程(稱為 TimerThread),該線程會(huì)不斷從任務(wù)隊(duì)列中取出最早需要執(zhí)行的 TimerTask 任務(wù),然后執(zhí)行該任務(wù)的 run () 方法。當(dāng)任務(wù)執(zhí)行完成后,線程會(huì)繼續(xù)等待下一個(gè)任務(wù)的執(zhí)行時(shí)間,如此循環(huán)往復(fù)。如果某個(gè)任務(wù)的執(zhí)行時(shí)間還未到,TimerThread 會(huì)進(jìn)入等待狀態(tài),直到到達(dá)任務(wù)的執(zhí)行時(shí)間或者有新的任務(wù)被添加進(jìn)來。 2. 實(shí)現(xiàn)步驟 使用 Timer 實(shí)現(xiàn)定時(shí)任務(wù)通常分為以下幾個(gè)步驟: 首先,創(chuàng)建一個(gè)繼承自 TimerTask 的類,并重寫其 run () 方法,在 run () 方法中編寫具體的定時(shí)任務(wù)邏輯。例如: 取消自動(dòng)換行復(fù)制 import java.util.TimerTask; 然后,創(chuàng)建 Timer 對(duì)象,并調(diào)用其 schedule () 方法來安排定時(shí)任務(wù)的執(zhí)行。Timer 提供了多個(gè)重載的 schedule () 方法,以滿足不同的定時(shí)需求。比如,延遲指定時(shí)間后執(zhí)行一次任務(wù): 取消自動(dòng)換行復(fù)制 import java.util.Timer; 也可以實(shí)現(xiàn)周期性執(zhí)行任務(wù),例如延遲 1 秒后,每隔 2 秒執(zhí)行一次任務(wù): 取消自動(dòng)換行復(fù)制 timer.schedule(new MyTimerTask(), 1000, 2000); 3. 優(yōu)缺點(diǎn)分析 Timer 的優(yōu)點(diǎn)在于使用簡(jiǎn)單、直觀,對(duì)于一些簡(jiǎn)單的定時(shí)任務(wù)場(chǎng)景,不需要引入額外的依賴,僅使用 JDK 自帶的類就可以快速實(shí)現(xiàn)。官網(wǎng):http://WWW.MUZHIHUA.CN/ 然而,Timer 存在不少明顯的缺點(diǎn)。首先,單線程執(zhí)行問題。Timer 內(nèi)部只有一個(gè)線程來執(zhí)行所有的定時(shí)任務(wù),如果某個(gè)任務(wù)執(zhí)行時(shí)間過長(zhǎng),會(huì)導(dǎo)致后續(xù)的任務(wù)延遲執(zhí)行。比如,一個(gè)任務(wù)原本應(yīng)該每隔 2 秒執(zhí)行一次,但其中一次執(zhí)行耗時(shí) 5 秒,那么下一次任務(wù)的執(zhí)行時(shí)間就會(huì)被推遲 3 秒。其次,異常影響問題。如果 TimerTask 任務(wù)在執(zhí)行過程中拋出了未捕獲的異常,會(huì)導(dǎo)致 TimerThread 線程終止,后續(xù)所有的定時(shí)任務(wù)都無法繼續(xù)執(zhí)行。這是因?yàn)?TimerThread 在執(zhí)行任務(wù)的 run () 方法時(shí),沒有對(duì)異常進(jìn)行捕獲處理,一旦出現(xiàn)未捕獲異常,線程就會(huì)直接退出。另外,時(shí)間精度問題。Timer 的定時(shí)精度依賴于系統(tǒng)的時(shí)鐘,并且受到線程調(diào)度的影響,在一些對(duì)時(shí)間精度要求較高的場(chǎng)景下,Timer 可能無法滿足需求。還有,不支持復(fù)雜的定時(shí)表達(dá)式。Timer 只能實(shí)現(xiàn)延遲執(zhí)行或固定周期執(zhí)行的定時(shí)任務(wù),對(duì)于像 “每個(gè)月最后一天的晚上 10 點(diǎn)執(zhí)行任務(wù)” 這樣復(fù)雜的定時(shí)需求,Timer 無法直接實(shí)現(xiàn)。 二、ScheduledExecutor:JDK 5 引入的更優(yōu)定時(shí)任務(wù)方案 為了解決 Timer 的不足,Java 5 版本在 java.util.concurrent 包下引入了 ScheduledExecutor 框架,它基于線程池實(shí)現(xiàn),提供了更強(qiáng)大、更可靠的定時(shí)任務(wù)調(diào)度功能。官網(wǎng):http://WWW.SCYDS.CN/ 1. 核心原理 ScheduledExecutor 的核心是 ScheduledThreadPoolExecutor 類,它繼承自 ThreadPoolExecutor,本質(zhì)上是一個(gè)支持定時(shí)任務(wù)調(diào)度的線程池。ScheduledThreadPoolExecutor 內(nèi)部維護(hù)了一個(gè)延遲隊(duì)列(DelayedWorkQueue),用于存儲(chǔ)待執(zhí)行的定時(shí)任務(wù)。當(dāng)向 ScheduledExecutor 提交一個(gè)定時(shí)任務(wù)時(shí),任務(wù)會(huì)被添加到延遲隊(duì)列中,隊(duì)列會(huì)按照任務(wù)的執(zhí)行時(shí)間對(duì)任務(wù)進(jìn)行排序。線程池中的工作線程會(huì)不斷從延遲隊(duì)列中獲取到期的任務(wù)并執(zhí)行。與 Timer 的單線程不同,ScheduledExecutor 可以通過設(shè)置線程池的核心線程數(shù),同時(shí)執(zhí)行多個(gè)定時(shí)任務(wù),避免了單個(gè)任務(wù)執(zhí)行時(shí)間過長(zhǎng)對(duì)其他任務(wù)的影響。官網(wǎng):http://WWW.HRBTDYY.CN/ 2. 實(shí)現(xiàn)步驟 使用 ScheduledExecutor 實(shí)現(xiàn)定時(shí)任務(wù)的步驟如下: 首先,通過 Executors 工具類的 newScheduledThreadPool () 方法創(chuàng)建 ScheduledExecutorService 對(duì)象,指定線程池的核心線程數(shù)。例如: 取消自動(dòng)換行復(fù)制 import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class ScheduledExecutorTest { public static void main(String[] args) { // 創(chuàng)建一個(gè)核心線程數(shù)為2的ScheduledExecutorService ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(2); } } 然后,調(diào)用 ScheduledExecutorService 的 schedule () 或 scheduleAtFixedRate ()、scheduleWithFixedDelay () 方法提交定時(shí)任務(wù)。其中,schedule () 方法用于延遲指定時(shí)間后執(zhí)行一次任務(wù);scheduleAtFixedRate () 方法用于延遲指定時(shí)間后,按照固定的周期執(zhí)行任務(wù),周期是從任務(wù)開始執(zhí)行的時(shí)間算起;scheduleWithFixedDelay () 方法也是延遲指定時(shí)間后周期性執(zhí)行任務(wù),但周期是從任務(wù)執(zhí)行完成的時(shí)間算起。例如,延遲 1 秒后,每隔 2 秒執(zhí)行一次任務(wù)(從任務(wù)開始執(zhí)行時(shí)間計(jì)算周期):官網(wǎng):http://WWW.WPTCPWM.CN/ 取消自動(dòng)換行復(fù)制 如果希望周期從任務(wù)執(zhí)行完成后開始計(jì)算,可以使用 scheduleWithFixedDelay () 方法: 取消自動(dòng)換行復(fù)制 scheduledExecutor.scheduleWithFixedDelay(() -> { System.out.println("ScheduledExecutor任務(wù)執(zhí)行,當(dāng)前時(shí)間:" + System.currentTimeMillis()); try { Thread.sleep(1000); // 模擬任務(wù)執(zhí)行耗時(shí)1秒 } catch (InterruptedException e) { e.printStackTrace(); } }, 1, 2, TimeUnit.SECONDS); 在這個(gè)例子中,任務(wù)延遲 1 秒后第一次執(zhí)行,執(zhí)行耗時(shí) 1 秒,執(zhí)行完成后,間隔 2 秒進(jìn)行下一次執(zhí)行,所以兩次任務(wù)開始執(zhí)行的時(shí)間間隔是 3 秒。 3. 優(yōu)缺點(diǎn)分析 ScheduledExecutor 相比 Timer 有諸多優(yōu)點(diǎn)。首先,多線程執(zhí)行。ScheduledExecutor 基于線程池,可以設(shè)置多個(gè)核心線程,多個(gè)定時(shí)任務(wù)可以并行執(zhí)行,避免了單個(gè)任務(wù)執(zhí)行時(shí)間過長(zhǎng)對(duì)其他任務(wù)的影響。即使某個(gè)任務(wù)執(zhí)行時(shí)間超過了周期,其他任務(wù)依然可以按照預(yù)定時(shí)間執(zhí)行。其次,異常處理機(jī)制更完善。如果某個(gè)定時(shí)任務(wù)拋出了未捕獲的異常,只會(huì)導(dǎo)致該任務(wù)所在的工作線程終止,但線程池會(huì)創(chuàng)建新的工作線程來替代終止的線程,繼續(xù)執(zhí)行其他的定時(shí)任務(wù),不會(huì)影響整個(gè)定時(shí)任務(wù)調(diào)度系統(tǒng)的正常運(yùn)行。不過,為了保證任務(wù)的穩(wěn)定執(zhí)行,開發(fā)者還是應(yīng)該在任務(wù)代碼中主動(dòng)捕獲并處理異常。再次,更高的時(shí)間精度。ScheduledExecutor 的定時(shí)精度相比 Timer 有了一定的提升,并且通過線程池的優(yōu)化調(diào)度,減少了線程調(diào)度對(duì)定時(shí)精度的影響,能夠滿足大多數(shù)場(chǎng)景下的時(shí)間精度要求。另外,支持靈活的任務(wù)調(diào)度方式。除了延遲執(zhí)行和固定周期執(zhí)行外,還可以通過 scheduleWithFixedDelay () 方法實(shí)現(xiàn)基于任務(wù)執(zhí)行完成時(shí)間的周期性調(diào)度,滿足更多樣化的定時(shí)需求。官網(wǎng):http://WWW.CHIJI8.CN/ ScheduledExecutor 的缺點(diǎn)主要在于不支持復(fù)雜的定時(shí)表達(dá)式。雖然它比 Timer 的功能更強(qiáng)大,但對(duì)于像 “每周一至周五的上午 9 點(diǎn)執(zhí)行任務(wù)”“每個(gè)季度的第一個(gè)工作日?qǐng)?zhí)行任務(wù)” 這樣復(fù)雜的定時(shí)需求,ScheduledExecutor 依然無法直接實(shí)現(xiàn),需要開發(fā)者自行編寫復(fù)雜的邏輯來計(jì)算任務(wù)的執(zhí)行時(shí)間。此外,對(duì)任務(wù)的管理功能相對(duì)簡(jiǎn)單。ScheduledExecutor 沒有提供豐富的任務(wù)管理接口,比如暫停任務(wù)、恢復(fù)任務(wù)、動(dòng)態(tài)修改任務(wù)的執(zhí)行周期等,在一些需要對(duì)任務(wù)進(jìn)行靈活管理的場(chǎng)景下,可能需要開發(fā)者自行實(shí)現(xiàn)相關(guān)功能。 三、Quartz:功能強(qiáng)大的開源定時(shí)任務(wù)框架 Quartz 是一個(gè)開源的、功能強(qiáng)大的定時(shí)任務(wù)調(diào)度框架,它提供了豐富的特性,能夠滿足各種復(fù)雜的定時(shí)任務(wù)需求,廣泛應(yīng)用于企業(yè)級(jí)應(yīng)用開發(fā)中。 1. 核心原理 Quartz 的核心組件包括 Scheduler(調(diào)度器)、Job(任務(wù))、JobDetail(任務(wù)詳情)、Trigger(觸發(fā)器)和 JobStore(任務(wù)存儲(chǔ))。Scheduler 是 Quartz 的核心調(diào)度器,負(fù)責(zé)管理和調(diào)度所有的定時(shí)任務(wù)。Job 是具體的定時(shí)任務(wù)邏輯實(shí)現(xiàn)類,開發(fā)者需要實(shí)現(xiàn) Job 接口,并在 execute () 方法中編寫任務(wù)邏輯。JobDetail 用于描述 Job 的詳細(xì)信息,包括 Job 的名稱、組名、描述等,它相當(dāng)于 Job 的實(shí)例化配置。Trigger 用于定義任務(wù)的觸發(fā)條件,即任務(wù)在什么時(shí)間執(zhí)行,Quartz 提供了多種類型的 Trigger,如 SimpleTrigger(簡(jiǎn)單觸發(fā)器,類似 ScheduledExecutor 的固定延遲或固定周期觸發(fā))和 CronTrigger(Cron 表達(dá)式觸發(fā)器,支持復(fù)雜的定時(shí)表達(dá)式)。JobStore 用于存儲(chǔ)任務(wù)和觸發(fā)器的相關(guān)信息,Quartz 支持多種類型的 JobStore,如 RAMJobStore(將任務(wù)信息存儲(chǔ)在內(nèi)存中,性能高但服務(wù)重啟后數(shù)據(jù)丟失)和 JDBCJobStore(將任務(wù)信息存儲(chǔ)在數(shù)據(jù)庫(kù)中,支持?jǐn)?shù)據(jù)持久化,服務(wù)重啟后任務(wù)可以繼續(xù)執(zhí)行)。官網(wǎng):http://WWW./ Quartz 的工作流程如下:首先,創(chuàng)建 Job 接口的實(shí)現(xiàn)類,編寫任務(wù)邏輯;然后,創(chuàng)建 JobDetail 對(duì)象,關(guān)聯(lián)對(duì)應(yīng)的 Job 類,并設(shè)置相關(guān)屬性;接著,創(chuàng)建 Trigger 對(duì)象,定義任務(wù)的觸發(fā)條件;之后,通過 SchedulerFactory 創(chuàng)建 Scheduler 實(shí)例;最后,將 JobDetail 和 Trigger 注冊(cè)到 Scheduler 中,Scheduler 會(huì)根據(jù) Trigger 的觸發(fā)條件,在合適的時(shí)間調(diào)度 Job 的 execute () 方法執(zhí)行任務(wù)。 2. 實(shí)現(xiàn)步驟 使用 Quartz 實(shí)現(xiàn)定時(shí)任務(wù),首先需要在項(xiàng)目中引入 Quartz 的依賴。如果是 Maven 項(xiàng)目,在 pom.xml 文件中添加以下依賴: 取消自動(dòng)換行復(fù)制 <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> 然后,實(shí)現(xiàn) Job 接口,編寫任務(wù)邏輯:官網(wǎng):http://WWW./ 取消自動(dòng)換行復(fù)制 import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; public class MyQuartzJob implements Job { @Override public void execute(JobExecutionContext context) throws JobExecutionException { // 定時(shí)任務(wù)執(zhí)行邏輯 System.out.println("Quartz任務(wù)執(zhí)行,當(dāng)前時(shí)間:" + System.currentTimeMillis()); // 可以通過JobExecutionContext獲取JobDetail和Trigger的相關(guān)信息 String jobName = context.getJobDetail().getKey().getName(); String triggerName = context.getTrigger().getKey().getName(); System.out.println("任務(wù)名稱:" + jobName + ",觸發(fā)器名稱:" + triggerName); } } 接下來,創(chuàng)建 Scheduler,并注冊(cè) JobDetail 和 Trigger: 取消自動(dòng)換行復(fù)制 3. 優(yōu)缺點(diǎn)分析 Quartz 的優(yōu)點(diǎn)非常突出。首先,支持復(fù)雜的定時(shí)表達(dá)式。借助 CronTrigger,Quartz 可以實(shí)現(xiàn)幾乎所有復(fù)雜的定時(shí)需求,Cron 表達(dá)式能夠精確到秒,支持按秒、分、時(shí)、日、月、周、年等維度設(shè)置定時(shí)規(guī)則,比如 “每個(gè)月的 1 號(hào)、15 號(hào)的下午 3 點(diǎn) 15 分執(zhí)行任務(wù)”“每周一至周五的上午 9 點(diǎn)到下午 5 點(diǎn),每隔 30 分鐘執(zhí)行一次任務(wù)” 等,極大地滿足了企業(yè)級(jí)應(yīng)用中復(fù)雜的定時(shí)調(diào)度需求。其次,強(qiáng)大的任務(wù)管理功能。Quartz 提供了豐富的 API 來管理定時(shí)任務(wù),包括任務(wù)的創(chuàng)建、刪除、暫停、恢復(fù)、修改觸發(fā)條件等。開發(fā)者可以通過 Scheduler 的相關(guān)方法,動(dòng)態(tài)地對(duì)任務(wù)進(jìn)行管理,例如,在應(yīng)用運(yùn)行過程中,根據(jù)業(yè)務(wù)需求動(dòng)態(tài)添加新的定時(shí)任務(wù),或者修改已有任務(wù)的執(zhí)行時(shí)間。再次,支持任務(wù)持久化。通過 JDBCJobStore,Quartz 可以將任務(wù)和觸發(fā)器的信息存儲(chǔ)在數(shù)據(jù)庫(kù)中,即使應(yīng)用服務(wù)重啟,任務(wù)信息也不會(huì)丟失,服務(wù)重啟后,Scheduler 可以從數(shù)據(jù)庫(kù)中恢復(fù)之前的任務(wù),并繼續(xù)按照預(yù)定的觸發(fā)條件執(zhí)行任務(wù),保證了定時(shí)任務(wù)的可靠性和連續(xù)性。另外,分布式支持。Quartz 支持分布式部署,多個(gè)應(yīng)用實(shí)例可以共享同一個(gè)數(shù)據(jù)庫(kù)中的任務(wù)信息,通過分布式鎖機(jī)制避免多個(gè)實(shí)例同時(shí)執(zhí)行同一個(gè)任務(wù),實(shí)現(xiàn)了定時(shí)任務(wù)的分布式調(diào)度,適用于集群環(huán)境下的定時(shí)任務(wù)需求。還有,豐富的擴(kuò)展功能。Quartz 提供了很多擴(kuò)展點(diǎn),開發(fā)者可以根據(jù)自己的需求自定義 JobFactory、TriggerListener、JobListener 等組件,擴(kuò)展 Quartz 的功能,滿足特定的業(yè)務(wù)場(chǎng)景。 Quartz 的缺點(diǎn)主要在于使用復(fù)雜度較高。相比 Timer 和 ScheduledExecutor,Quartz 的組件更多,配置更復(fù)雜,需要開發(fā)者了解 Job、JobDetail、Trigger、Scheduler 等多個(gè)組件的作用和使用方式,對(duì)于簡(jiǎn)單的定時(shí)任務(wù)場(chǎng)景,使用 Quartz 會(huì)顯得過于繁瑣。其次,依賴引入。Quartz 是一個(gè)第三方框架,使用時(shí)需要在項(xiàng)目中引入相關(guān)的依賴包,增加了項(xiàng)目的依賴復(fù)雜度,而 Timer 和 ScheduledExecutor 是 JDK 自帶的,無需額外引入依賴。另外,性能開銷。由于 Quartz 提供了豐富的功能和持久化機(jī)制,在一些簡(jiǎn)單的定時(shí)任務(wù)場(chǎng)景下,其性能開銷相對(duì) Timer 和 ScheduledExecutor 會(huì)更大一些。官網(wǎng):http://WWW./ 四、三種定時(shí)任務(wù)方式的對(duì)比與選型建議 1. 多維度對(duì)比 |
|