欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java中實現定時任務的兩種方法舉例詳解

 更新時間:2024年12月21日 11:52:20   作者:水w  
這篇文章主要給大家介紹了關于Java中實現定時任務的兩種方法,文中總結了各種實現方式的優(yōu)缺點,并給出了推薦的使用場景,通過代碼介紹的非常詳細,需要的朋友可以參考下

一、定時任務

概念

定時任務是一種自動化執(zhí)行特定操作的方式,可以根據預定的時間、日期或間隔周期性地執(zhí)行某些任務。

在平常的生活中,大家肯定是有設置鬧鐘的習慣,我們需要通過鬧鐘來提醒我們到這個時刻,我們應該做指定的事情。同樣的在編程當中,我們很多時候也是需要實現這樣的操作的,到達指定的時刻,我們想要我們的程序去執(zhí)行某一個事情,比如:指定時間發(fā)送郵箱、指定時間發(fā)送生日祝福……

以上的種種到達指定時間做指定事情,就是定時任務。

作用

  • 自動化任務執(zhí)行:定時任務能夠在預定的時間觸發(fā)執(zhí)行某些任務,無需人工干預。這對于需要定期執(zhí)行的重復性任務非常有效,例如數據備份、統(tǒng)計報表生成、系統(tǒng)維護等。
  • 提高效率和準確性:通過定時任務,可以在特定的時間段內自動執(zhí)行任務,避免了人工操作的疏忽和錯誤。這樣可以提高任務的執(zhí)行效率和準確性,并降低因人為原因導致的錯誤風險。
  • 節(jié)省時間和資源:定時任務可以代替人工手動執(zhí)行的操作,節(jié)省了大量人力資源和時間成本。同時,它也可以合理分配系統(tǒng)資源,避免任務集中導致的系統(tǒng)負載過高。
  • 異步執(zhí)行:定時任務可以在后臺異步執(zhí)行,不會阻塞用戶的其他操作。這對于需要執(zhí)行耗時較長的任務或需要長時間運行的操作非常有用,可以提高系統(tǒng)的響應速度和用戶體驗。

二、簡單定時任務實現方式

今天我們來討論一下在Java中如何實現定時任務。定時任務在很多場景下都非常有用,例如定期執(zhí)行清理工作、數據備份、發(fā)送通知等。

在Java中,常見的可以實現定時任務的方式有如下幾種:

(1)線程類實現定時任務:比如Thread、Runnable、Callable等線程類都可以實現定時任務。

(2)Timer/TimerTask:Java提供了java.util.Timer和java.util.TimerTask類,可以用于創(chuàng)建定時任務。通過創(chuàng)建一個Timer對象,并調用其schedule()方法,可以指定任務的執(zhí)行時間和執(zhí)行間隔。然后,創(chuàng)建一個繼承自TimerTask的子類,實現具體的任務邏輯,并在run()方法中定義需要執(zhí)行的代碼。最后,將該任務對象通過Timer的schedule()方法進行調度即可。

(3)ScheduledExecutorService:Java提供了java.util.concurrent.ScheduledExecutorService接口,可以用于創(chuàng)建定時任務。通過調用ScheduledExecutorService的scheduleAtFixedRate()或scheduleWithFixedDelay()方法,可以指定任務的執(zhí)行時間和執(zhí)行間隔。然后,創(chuàng)建一個實現了Runnable接口的類,實現具體的任務邏輯,并在run()方法中定義需要執(zhí)行的代碼。最后,將該任務對象提交給ScheduledExecutorService進行調度即可。

(4)@Scheduled注解:這個是Spring框架所提供的,通過在方法上添加@Scheduled注解,并設置相應的時間表達式,就可以讓方法按照指定的時間間隔自動執(zhí)行。

1. Thread線程等待(最原始最簡單方式)

創(chuàng)建一個thread,然后讓它在while循環(huán)里一直運行著,通過sleep方法來達到定時任務的效果。

/**
 * 匿名內部類實現 java.lang.Runnable 接口
 */
public class ThreadTask {
    public static void main(String[] args) {
        final long timeInterval = 1000;
 
        //創(chuàng)建線程(匿名內部類方式)
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
                    String dateStr = sdf.format(new Date());
                    System.out.println("線程等待實現定時任務:" + dateStr);
 
                    try {
                        Thread.sleep(timeInterval);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        //開啟線程
        thread.start();
    }
}
public class ThreadTask1 {
    public static void main(String[] args) {
        MyRunnable runnable = new MyRunnable();
        //創(chuàng)建線程(自定義類MyRunnable實現java.lang.Runnable接口)
        Thread t = new Thread(runnable);
        //開啟線程
        t.start();
    }
}
 
/**
 * 自定義類MyRunnable實現java.lang.Runnable接口
 */
class MyRunnable implements Runnable{
    final long timeInterval = 1000;
 
    @Override
    public void run() {
        while (true){
            SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
            String dateStr = sdf.format(new Date());
            System.out.println("線程等待實現定時任務1:" + dateStr);
 
            try {
                Thread.sleep(timeInterval);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

2. 使用java.util.Timer

JDK自帶的Timer API算是最古老的定時任務實現方式了。Timer是一種定時器工具,使用java.util.Timer工具類。用來在一個后臺線程計劃執(zhí)行指定任務。它可以安排任務“執(zhí)行一次”或者定期“執(zhí)行多次”。

Timer類核心方法如下:

// 在指定延遲時間后執(zhí)行指定的任務
schedule(TimerTask task,long delay);
 
// 在指定時間執(zhí)行指定的任務。(只執(zhí)行一次)
schedule(TimerTask task, Date time);
 
// 延遲指定時間(delay)之后,開始以指定的間隔(period)重復執(zhí)行指定的任務
schedule(TimerTask task,long delay,long period);
 
// 在指定的時間開始按照指定的間隔(period)重復執(zhí)行指定的任務
schedule(TimerTask task, Date firstTime , long period);
 
// 在指定的時間開始進行重復的固定速率執(zhí)行任務
scheduleAtFixedRate(TimerTask task,Date firstTime,long period);
 
// 在指定的延遲后開始進行重復的固定速率執(zhí)行任務
scheduleAtFixedRate(TimerTask task,long delay,long period);
 
// 終止此計時器,丟棄所有當前已安排的任務。
cancal();
 
// 從此計時器的任務隊列中移除所有已取消的任務。
purge();
 
import java.util.Timer;
import java.util.TimerTask;

public class TimerExample {
    public static void main(String[] args) {
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                System.out.println("Task executed at: " + System.currentTimeMillis());
            }
        };
        
        Timer timer = new Timer();
        // 安排任務在1秒后執(zhí)行,并且每隔1秒執(zhí)行一次
        timer.scheduleAtFixedRate(task, 1000, 1000);
    }
}

在這個示例中,我們創(chuàng)建了一個Timer對象,并用scheduleAtFixedRate方法安排一個TimerTask在1秒后開始執(zhí)行,并且每隔1秒執(zhí)行一次。

Timer 優(yōu)缺點分析

優(yōu)點:JDK自帶的,簡單易用。

缺點:

(1)對系統(tǒng)時間敏感

Timer類的任務調度是基于絕對時間的,而不是相對時間,所以它對系統(tǒng)時間的改變非常敏感。當系統(tǒng)時間發(fā)生變化時,可能導致任務執(zhí)行時間的誤差。

(2)不適合高并發(fā)場景

由于Timer類使用單個線程執(zhí)行所有任務,不適合在高并發(fā)環(huán)境下使用。當任務過多或任務執(zhí)行時間較長時,會影響整體性能和響應性。

(3)任務的無法持久化

當應用程序關閉或重啟時,Timer 中已經調度的任務會丟失。

(4)單線程執(zhí)行

Timer類內部使用單個線程來執(zhí)行所有的定時任務。如果某個任務執(zhí)行時間過長,會影響其他任務的執(zhí)行,可能導致任務被延遲。

當一個任務的執(zhí)行時間過長時,會影響其他任務的調度。

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;

/**
 * @author water
 * @date 2024/10/5
 */
public class Main {
    public static void main(String[] args) {
        // 定時任務1
        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
                String dateStr = sdf.format(new Date());
                System.out.println("進入定時任務1:" + dateStr);
                // 休眠5秒
                try {TimeUnit.SECONDS.sleep(5);}
                catch (InterruptedException e) {e.printStackTrace();}
                dateStr = sdf.format(new Date());
                System.out.println("運行定時任務1:" + dateStr);
            }
        };

        // 定時任務2
        TimerTask timerTask2 = new TimerTask() {
            @Override
            public void run() {
                SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
                String dateStr = sdf.format(new Date());
                System.out.println("-----進入定時任務2:" + dateStr);
                dateStr = sdf.format(new Date());
                System.out.println("-----運行定時任務2:" + dateStr);
            }
        };

        // 計時器
        Timer timer = new Timer();
        // 添加執(zhí)行任務(延遲 1s 執(zhí)行,每 2s 執(zhí)行一次)
        timer.schedule(timerTask, 1000, 2000);
        timer.schedule(timerTask2, 1000, 2000);
    }
}

這段代碼展示了如何使用Java的TimerTimerTask類來實現定時任務的調度。以下是對代碼的分析:

timerTask安排在延遲1秒后執(zhí)行,隨后每2秒執(zhí)行一次。 將timerTask2也安排在延遲1秒后執(zhí)行,隨后每2秒執(zhí)行一次。

定時任務1第一次運行時會在1秒后進入并輸出時間。由于在run()方法中調用了sleep(3),這意味著此任務在執(zhí)行期間會阻塞3秒。這會導致timerTask的后續(xù)執(zhí)行被延遲。

定時任務2將在1秒后運行,并每2秒執(zhí)行一次,但由于定時任務1在運行時阻塞了線程,可能會影響任務2的執(zhí)行頻率。

代碼的執(zhí)行結果如下,

任務調度的具體過程:

  • 剛開始主程序啟動。
  • 在時間是22:14:23.108時,任務1timerTask第一次執(zhí)行,打印“進入定時任務1”字符串。任務2也被調度開始執(zhí)行,但由于是單線程,任務2必須等待任務1完成。
  • 在時間22:14:23.108到22:14:28.115時,任務1繼續(xù)執(zhí)行, 并休眠5秒,打印“運行定時任務1”字符串。此時任務2還是處于等待狀態(tài)。
  • 在時間是22:14:28.115時,任務1完成。然后此時任務2就開始執(zhí)行,打印“進入定時任務2”和“運行定時任務2”字符串。
  • 在時間是22:14:28.116時,因為初始的執(zhí)行間隔為2秒,所以任務1再次被調度,打印“進入定時任務1”字符串。但由于被調度再次執(zhí)行的任務1仍在執(zhí)行,任務2再次處于等待狀態(tài)。
  • 在時間是22:14:28.116到22:14:33.110時,任務1繼續(xù)執(zhí)行, 并休眠5秒,打印“運行定時任務1”字符串。
  • .....

當任務 1 運行時間超過設定的間隔時間時,任務 2 也會延遲執(zhí)行 原本任務 1 和任務 2 的執(zhí)行時間間隔都是 2s,但因為任務 1 執(zhí)行了 5s,因此任務 2 的執(zhí)行時間間隔也變成了10秒(和原定時間不符)。

(5)錯誤處理能力有限

Timer線程是不會捕獲異常的,如果TimerTask拋出的了未檢查異常則會導致Timer線程終止,同時Timer也不會重新恢復線程的執(zhí)行,它會錯誤的認為整個Timer線程都會取消。同時,已經被安排單尚未執(zhí)行的TimerTask也不會再執(zhí)行了,新的任務也不能被調度。因此如果TimerTask拋出未檢查的異常,Timer將會產生無法預料的行為。

(6)任務異常影響其他任務

使用 Timer 類實現定時任務時,當一個任務拋出異常,其他任務也會終止運行。

Timer線程是不會捕獲異常的,如果TimerTask拋出的了未檢查異常則會導致Timer線程終止,同時Timer也不會重新恢復線程的執(zhí)行,它會錯誤的認為整個Timer線程都會取消。同時,已經被安排單尚未執(zhí)行的TimerTask也不會再執(zhí)行了,新的任務也不能被調度。

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

/**
 * @author water
 * @date 2024/10/5
 */
public class Main {
    public static void main(String[] args) {
        // 定時任務1
        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
                String dateStr = sdf.format(new Date());
                System.out.println("進入定時任務1:" + dateStr);
                //發(fā)生異常
                int num = 10 / 0;
                dateStr = sdf.format(new Date());
                System.out.println("運行定時任務1:" + dateStr);
            }
        };

        // 定時任務2
        TimerTask timerTask2 = new TimerTask() {
            @Override
            public void run() {
                SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
                String dateStr = sdf.format(new Date());
                System.out.println("----進入定時任務2:" + dateStr);
                dateStr = sdf.format(new Date());
                System.out.println("----運行定時任務2:" + dateStr);
            }
        };
        // 計時器
        Timer timer = new Timer();
        // 添加執(zhí)行任務(延遲 1s 執(zhí)行,每 2s 執(zhí)行一次)
        timer.schedule(timerTask, 1000, 2000);
        timer.schedule(timerTask2, 1000, 2000);
    }
}

代碼的執(zhí)行結果如下, 

3. 使用JDK自帶的ScheduledExecutorService

ScheduledExecutorService是Java并發(fā)包(java.util.concurrent)中的一個接口, 是JAVA 1.5后新增的定時任務接口,它是基于線程池設計的定時任務類,每個調度任務都會分配到線程池中的一個線程去執(zhí)行(任務是并發(fā)執(zhí)行,互不影響)。

ScheduledExecutorService可以實現Timer具備的所有功能,并解決了 Timer類存在的問題提供了比Timer更強大的定時任務調度功能。它可以調度任務在給定的延遲后運行,或者周期性地執(zhí)行。

注意:只有當執(zhí)行調度任務時,ScheduledExecutorService才會真正啟動一個線程,其余時間ScheduledExecutorService都是出于輪詢任務的狀態(tài)。

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * @author water
 * @date 2024/10/5
 */
public class Main {
    public static void main(String[] args) {
        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
        Runnable task = new Runnable() {
            @Override
            public void run() {
                String dateTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
                System.out.println("執(zhí)行任務的時間:" + dateTime);
            }
        };
        // 安排任務在1秒后執(zhí)行,并且每隔1秒執(zhí)行一次
        scheduler.scheduleAtFixedRate(task, 1, 1, TimeUnit.SECONDS);
    }
}

在這個示例中,我們創(chuàng)建了一個ScheduledExecutorService對象,并用scheduleAtFixedRate方法安排一個任務在1秒后開始執(zhí)行,并且每隔1秒執(zhí)行一次。

 schedule和scheduleAtFixedRate的區(qū)別

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

  • 任務執(zhí)行未超時,下次執(zhí)行時間 = 上次執(zhí)行開始時間 + period。

  • 任務執(zhí)行超時,下次執(zhí)行時間 = 上次執(zhí)行結束時間。

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

它們的不同點在于側重點不同

  • schedule方法側重保持間隔時間的穩(wěn)定。
  • scheduleAtFixedRate方法更加側重于保持執(zhí)行頻率的穩(wěn)定。

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

schedule是固定延遲,更加側重保持延遲間隔的固定性。每次都是以上一個任務的起始時間來判斷時間間隔。

schedule方法會因為前一個任務的延遲而導致其后面的定時任務延時。計算公式為scheduledExecutionTime(第n+1次) = realExecutionTime(第n次) + periodTime。

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

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

// 延遲1s后開始執(zhí)行任務,然后每隔2秒執(zhí)行
timer.schedule(task, 1000, 2000);
  • 第0~1秒,等待狀態(tài);
  • 第1秒,第一個任務開始執(zhí)行,執(zhí)行耗時3秒;
  • 計算第二個任務的預定執(zhí)行時間:第一個任務的起始執(zhí)行時間 + 任務執(zhí)行周期兩秒鐘 = 1+2=3,所以第3秒是第二個任務的預定執(zhí)行時間;
  • 第4秒,第一個任務執(zhí)行完畢,但是發(fā)現當前時間已經超過了第二個任務的預定執(zhí)行時間,所以第二個任務立即執(zhí)行,第二個任務的執(zhí)行時間是1秒鐘;
  • 計算第三個任務的預定執(zhí)行時間:第二個任務起始執(zhí)行時間+任務執(zhí)行周期兩秒鐘=4+2=6,所以第三個任務是預定在第6秒執(zhí)行;
  • 第5秒鐘,第二個任務執(zhí)行完畢,發(fā)現當前是第5秒,還未到第6秒,所以還需要等待1秒鐘。

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

scheduleAtFixedRate是固定速率,更加側重保持執(zhí)行頻率的穩(wěn)定性。scheduleAtFixedRate當前任務到達規(guī)定時間一定執(zhí)行,上一個未執(zhí)行的任務會直接終止。

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

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

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

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

簡而言之:schedule的策略是錯過了就錯過了,后續(xù)按照新的節(jié)奏來走;scheduleAtFixedRate的策略是如果錯過了,就努力追上原來的節(jié)奏。

4. 使用SpringTask實現定時任務

從Spring 3開始,Spring自帶了一套定時任務工具Spring-Task(基于注解 @Scheduled,@EnableScheduling 形式實現),可以把它看成是一個輕量級的Quartz,使用起來十分簡單,除Spring相關的包外不需要額外的包,支持注解和配置文件兩種形式。通常情況下在Spring體系內,針對簡單的定時任務,可直接使用Spring提供的功能。

如果你在使用Spring框架,可以利用@Scheduled注解來方便地實現定時任務。首先,需要確保你的Spring配置中啟用了任務調度功能。如果是在Spring Boot項目中,需要在啟動類上添加@EnableScheduling來開啟定時任務。

以 Spring Boot 為例,實現定時任務只需兩步:

  • 開啟定時任務
  • 添加定時任務

(1)開啟定時任務

如果是在Spring Boot項目中,需要在啟動類上添加@EnableScheduling來開啟定時任務

@EnableScheduling // 開啟定時任務
@SpringBootApplication
public class Job4ScheduledApplication {
    public static void main(String[] args) {
        SpringApplication.run(Job4ScheduledApplication.class, args);
    }
}

(2)添加定時任務

定時任務的添加只需要使用 @Scheduled 注解標注即可,如果有多個定時任務可以創(chuàng)建多個 @Scheduled 注解標注的方法。

@Component  //@Component用于實例化類,將其類托管給 Spring 容器
public class TaskJobUtil {
    /**
     * cron表達式:表示每2秒 執(zhí)行任務
     */
    @Scheduled(cron = "0/2 * * * * ?")
    public void task() {
        System.out.println("task0-start");
        sleep(5);
        System.out.println("task0-end");
    }
 
    /**
     * fixedRate:每間隔2秒執(zhí)行一次任務
     * 注意,默認情況下定時任務是在同一線程同步執(zhí)行的,如果任務的執(zhí)行時間(如5秒)大于間隔時間,則會等待任務執(zhí)行結束后直接開始下次任務
     */
    @Scheduled(fixedRate = 2000)
    public void task0() {
        System.out.println("task0-start");
        sleep(5);
        System.out.println("task0-end");
    }
 
    /**
     * fixedDelay:每次延時2秒執(zhí)行一次任務
     * 注意,這里是等待上次任務執(zhí)行結束后,再延時固定時間后開始下次任務
     */
    @Scheduled(fixedDelay = 2000)
    public void task1() {
        System.out.println("task1-start");
        sleep(5);
        System.out.println("task1-end");
    }
 
    /**
     * initialDelay:首次任務啟動的延時時間
     */
    @Scheduled(initialDelay = 2000, fixedDelay = 3000)
    public void task2() {
        System.out.println("task2-start");
        sleep(5);
        System.out.println("task2-end");
    }
 
    private void sleep(long time) {
        try {
            TimeUnit.SECONDS.sleep(time);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

三、分布式定時任務實現方式

前面所有的定時任務,無論是基于線程類,還是基于 JDK 自帶的定時任務,還是基于Spring提供的Spring Task,都無法在分布式環(huán)境下使用,并且不支持持久化,一旦服務重啟所有的定時任務都將發(fā)生丟失,所以我們需要使用到其它的第三方成熟的定時任務框架。

1. Quartz

除了JDK自帶的API之外,我們還可以使用開源的框架來實現,比如Quartz。Quartz是一個開源的任務調度庫,用于在Java應用程序中實現定時任務調度和作業(yè)調度。,它允許開發(fā)者通過配置或編程方式定義、調度和管理任務。

使用Quartz可以開發(fā)一個或者多個定時任務,每個定時任務可以單獨指定執(zhí)行的時間,例如每隔1小時執(zhí)行一次、每個月第一天上午10點執(zhí)行一次、每個月最后一天下午5點執(zhí)行一次等。

Quartz既可以單獨使用也可以跟spring框架整合使用,在實際開發(fā)中一般會使用后者。

(1)Quartz的核心功能包括:

  • 任務調度:定義任務的執(zhí)行計劃,并在指定時間或周期性執(zhí)行任務。
  • 任務管理:管理和控制任務的生命周期,如啟動、暫停、刪除等。
  • 持久化:支持將任務的狀態(tài)持久化到數據庫,以便在應用重啟后恢復任務狀態(tài)。

(2)Quartz架構圖如下:

Quartz主要由以下幾個核心組件組成:

  • Scheduler:調度器,是Quartz的核心,用于管理和調度任務。
  • Job:任務接口,定義任務的執(zhí)行邏輯,即具體要執(zhí)行的任務。所有Quartz任務必須實現這個接口。
  • JobDetail:任務細節(jié)對象,定義了任務的具體實現和執(zhí)行參數。
  • Trigger:觸發(fā)器,定義了任務的觸發(fā)條件,如時間、周期等。
    • SimpleTrigger
    • CronTirgger:和 Unix 的 cron 機制基本一樣,基于通用的公歷。
    • DateIntervalTrigger
    • NthIncludedDayTrigger
  • JobDataMap:任務數據映射,用于傳遞任務執(zhí)行時所需的數據。

JobDetail就是對job的定義,而job是具體執(zhí)行的邏輯內容。 具體的執(zhí)行的邏輯需要實現 job類,并實現execute方法。如果使用JobDetail來定義,那么每次調度都會創(chuàng)建一個new job實例,這樣帶來的好處就是任務并發(fā)執(zhí)行的時候,互不干擾,不會對臨界資源造成影響。

(3)Quartz的使用步驟

使用Quartz進行定時任務調度通常包括以下步驟:

  • 創(chuàng)建任務類:實現Job接口,定義任務的執(zhí)行邏輯。
  • 配置調度器:創(chuàng)建并配置Scheduler實例。
  • 定義任務細節(jié):創(chuàng)建JobDetail對象,指定任務類及其參數。
  • 定義觸發(fā)器:創(chuàng)建Trigger對象,指定任務的觸發(fā)條件。
  • 啟動調度器:將任務細節(jié)和觸發(fā)器注冊到調度器,并啟動調度器。

示例:使用Quartz進行定時任務調度

以下是一個使用Quartz進行定時任務調度的完整示例:

(1)創(chuàng)建任務類

在這個示例中,HelloJob類實現了Job接口,定義了任務的執(zhí)行邏輯,即打印一條消息。

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

public class HelloJob implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        System.out.println("Hello, Quartz!");
    }
}

HelloJob 類該類實現了 Job 接口。實現了Quartz 調度器調用的核心方法 execute 方法。

execute 方法的JobExecutionContext context 參數允許作業(yè)訪問調度上下文中的信息,如觸發(fā)器、調度器等。在方法體內,使用 System.out.println("Hello, Quartz!"); 打印一條簡單的消息,表示作業(yè)被執(zhí)行。

(2)配置調度器

在這個示例中,我們創(chuàng)建了一個調度器,并定義了一個任務和一個觸發(fā)器。任務HelloJob每10秒執(zhí)行一次,并在控制臺上打印消息。

import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.JobDataMap;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.CronScheduleBuilder;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.CronScheduleBuilder;
import org.quartz.SimpleScheduleBuilder;

public class QuartzExample {
    public static void main(String[] args) {
        try {
            // 創(chuàng)建調度器工廠
            SchedulerFactory schedulerFactory = new org.quartz.impl.StdSchedulerFactory();
            Scheduler scheduler = schedulerFactory.getScheduler();

            // 定義任務細節(jié)
            JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
                .withIdentity("myJob", "group1")
                .usingJobData("key", "value") // 傳遞任務數據
                .build();

            // 定義觸發(fā)器
            Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("myTrigger", "group1")
                .startNow()
                .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                    .withIntervalInSeconds(10) // 每10秒執(zhí)行一次
                    .repeatForever())
                .build();

            // 將任務細節(jié)和觸發(fā)器注冊到調度器
            scheduler.scheduleJob(jobDetail, trigger);
            // 啟動調度器
            scheduler.start();
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }
}

在這個示例中,我們創(chuàng)建一個調度器工廠的實例schedulerFactory,使用默認的標準調度器工廠。從調度器工廠獲取一個調度器實例scheduler,用于安排和執(zhí)行任務。

然后,創(chuàng)建一個新的任務細節(jié),指定作業(yè)類為 HelloJob。該類應該實現 org.quartz.Job 接口。為任務指定唯一的標識符,名稱為 "myJob",組名為 "group1"。通過 JobDataMap 向任務傳遞參數,方便在作業(yè)執(zhí)行時使用。構建最終的 JobDetail 對象。

創(chuàng)建一個新的觸發(fā)器構建器實例,為觸發(fā)器指定唯一的標識符,名稱為 "myTrigger",組名為 "group1",設置觸發(fā)器為立即開始執(zhí)行。使用簡單調度器定義觸發(fā)規(guī)則:設置觸發(fā)器每 10 秒執(zhí)行一次,并且使觸發(fā)器無限期重復執(zhí)行。構建最終的 Trigger 對象。

將任務和觸發(fā)器注冊到調度器中,使其能夠根據觸發(fā)器的調度規(guī)則執(zhí)行任務。

啟動調度器,使其開始調度任務。

(3)使用Cron表達式

Quartz支持使用Cron表達式來定義更復雜的觸發(fā)條件。Cron表達式是一種字符串格式,用于表示任務的觸發(fā)時間。以下是一個使用Cron表達式的示例:

Trigger cronTrigger = TriggerBuilder.newTrigger()
    .withIdentity("myCronTrigger", "group1")
    .withSchedule(CronScheduleBuilder.cronSchedule("0 0/5 * * * ?")) // 每5分鐘執(zhí)行一次
    .build();

在這個示例中,創(chuàng)建了一個名為 "myCronTrigger" 的 Cron 觸發(fā)器,它每 5 分鐘觸發(fā)一次。Cron表達式"0 0/5 * * * ?"表示任務將在每5分鐘的開始時刻執(zhí)行一次。

Quartz的持久化

Quartz支持將任務的狀態(tài)持久化到數據庫,以便在應用重啟后恢復任務狀態(tài)。要使用持久化功能,需要配置Quartz的持久化存儲。

(1)配置持久化存儲

quartz.properties文件中配置數據庫連接和持久化存儲,

org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.dataSource = myDS
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.isClustered = true

還需要配置數據源myDS,以便Quartz能夠連接到數據庫。

(2)數據庫表

Quartz提供了創(chuàng)建數據庫表的SQL腳本,可以在Quartz官網下載。執(zhí)行這些腳本將創(chuàng)建Quartz所需的表。

2. XXL-Job

XXL-Job是一個輕量級分布式任務調度平臺。特點是平臺化,易部署,開發(fā)迅速、學習簡單、輕量級、易擴展。由調度中心和執(zhí)行器功能完成定時任務的執(zhí)行。調度中心負責統(tǒng)一調度,執(zhí)行器負責接收調度并執(zhí)行。

3. Elastic-Job

Elastic-Job是一個開源的分布式任務調度解決方案,它是基于Java的輕量級分布式調度框架。

比較

三者的比較

  • 功能和特性:
    • Quartz:Quartz是一個功能強大的作業(yè)調度框架,支持靈活的任務調度策略、分布式集群、任務持久化等特性。它具有豐富的API和擴展點,可以根據需求進行定制開發(fā)和擴展。
    • XXL-Job:XXL-Job是一個分布式任務調度平臺,提供了可視化操作界面、多種任務調度方式、分片任務支持等特性。它注重于任務的管理和監(jiān)控,并提供了報警與告警功能。
    • Elastic-Job:Elastic-Job是一個輕量級的分布式任務調度解決方案,支持分布式任務調度、彈性擴縮容、任務監(jiān)控和管理等特性。它注重于任務的彈性擴展和容錯機制。
  • 分布式支持:
    • Quartz:Quartz在分布式場景中需要基于數據庫鎖來保證操作的唯一性,通過多個節(jié)點的異步運行實現高可用性。但它沒有執(zhí)行層面的任務分片機制。
    • XXL-Job:XXL-Job提供了分布式集群的支持,可以實現任務的負載均衡和高可用性。它支持分片任務和動態(tài)調整任務節(jié)點數量的特性。
    • Elastic-Job:Elastic-Job支持分布式任務調度,具備彈性擴縮容能力,可以根據任務的執(zhí)行情況動態(tài)調整任務節(jié)點數量。
  • 可視化和管理界面:
    • Quartz:Quartz本身沒有提供可視化的任務管理界面,需要通過其他工具或自行開發(fā)來實現。
    • XXL-Job:XXL-Job提供了簡潔直觀的任務管理界面,方便用戶進行任務的創(chuàng)建、編輯、狀態(tài)查看等操作。
    • Elastic-Job:Elastic-Job提供了任務監(jiān)控和管理功能,可以查看任務的執(zhí)行日志、運行狀態(tài)、統(tǒng)計信息等。
  • 社區(qū)活躍度和生態(tài)系統(tǒng):
    • Quartz:Quartz是一個非常成熟且廣泛使用的作業(yè)調度框架,擁有強大的社區(qū)支持和豐富的生態(tài)系統(tǒng)。
    • XXL-Job:XXL-Job也有一個活躍的社區(qū),并且在國內得到廣泛應用和認可。
    • Elastic-Job:Elastic-Job相對較新,并且社區(qū)規(guī)模較小,但其在分布式任務調度領域有一定的影響力。
  • 應用場景:
    • Quartz在功能和擴展性上非常強大,適用于復雜的任務調度需求。
    • XXL-Job注重于任務管理和監(jiān)控,并提供了可視化的操作界面。
    • Elastic-Job輕量級且具備分布式任務調度和彈性擴縮容能力。

四、總結

(1)線程+休眠實現定時任務,是最簡單實現定時任務的方式了,但這只是提供一種思路,實習開發(fā)中幾乎不會使用。

(2)JDK自帶的定時任務Timer和ScheduledExecutorService,我們需要了解兩者的區(qū)別。

  • Timer是單線程的,一旦發(fā)生異常,將終止所有的任務;Timer是絕對時間的,會受到系統(tǒng)時間的影響。
  • ScheduledExecutorService是基于線程池,是多線程的,一旦發(fā)生異常,不會終止所有的任務;ScheduledExecutorService是相對時間 ,不會受到系統(tǒng)時間的影響。
  • 注意區(qū)固定間隔和固定頻率的區(qū)別。

(3)Spring Task實現的定時任務是基于線程池,是多線程的,一旦發(fā)生異常,不會終止所有的任務;基于相對時間,不會受到系統(tǒng)時間的影響。

(4)分布式定時任務,一般是直接使用第三方成熟的定時任務框架,當然如果你公司資金充足可以選擇開發(fā)定制化定時任務框架。選用開源的第三方成熟定時任務框架,好處在于功能完善、免費,代碼質量也是有保障的。

如果你當前系統(tǒng)比較小,或者說沒那么在意可靠性,可以選用 JDK自帶的定時任務或者是SpringTask,否則就選用分布式定時任務框架,輕量級就可以選用 XXL-Job,大型系統(tǒng)可以選用Quartz。

到此這篇關于Java中實現定時任務的兩種方法的文章就介紹到這了,更多相關Java實現定時任務內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • mybatis多對多關聯實戰(zhàn)教程(推薦)

    mybatis多對多關聯實戰(zhàn)教程(推薦)

    下面小編就為大家?guī)硪黄猰ybatis多對多關聯實戰(zhàn)教程(推薦)。小編覺得挺不錯的,現在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-10-10
  • SpringBoot屬性綁定與bean屬性校驗實現方法詳解

    SpringBoot屬性綁定與bean屬性校驗實現方法詳解

    這篇文章主要介紹了SpringBoot屬性綁定與bean屬性校驗實現方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧
    2022-11-11
  • IntelliJ IDEA中顯示和關閉工具欄與目錄欄的方法

    IntelliJ IDEA中顯示和關閉工具欄與目錄欄的方法

    今天小編就為大家分享一篇關于IntelliJ IDEA中顯示和關閉工具欄與目錄欄的方法,小編覺得內容挺不錯的,現在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2018-10-10
  • img 加載網絡圖片失敗 顯示默認圖片的方法

    img 加載網絡圖片失敗 顯示默認圖片的方法

    下面小編就為大家?guī)硪黄猧mg 加載網絡圖片失敗 顯示默認圖片的方法。小編覺得挺不錯的,現在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-05-05
  • SpringCloud通過Feign傳遞List類型參數方式

    SpringCloud通過Feign傳遞List類型參數方式

    這篇文章主要介紹了SpringCloud通過Feign傳遞List類型參數方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • Java并發(fā)編程:CountDownLatch與CyclicBarrier和Semaphore的實例詳解

    Java并發(fā)編程:CountDownLatch與CyclicBarrier和Semaphore的實例詳解

    這篇文章主要介紹了Java并發(fā)編程:CountDownLatch與CyclicBarrier和Semaphore的實例詳解的相關資料,需要的朋友可以參考下
    2017-09-09
  • 使用Iterator刪除List中的多個元素操作

    使用Iterator刪除List中的多個元素操作

    這篇文章主要介紹了使用Iterator刪除List中的多個元素操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-09-09
  • Springboot+ElementUi實現評論、回復、點贊功能

    Springboot+ElementUi實現評論、回復、點贊功能

    這篇文章主要介紹了通過Springboot ElementUi實現評論、回復、點贊功能。如果是自己評論的還可以刪除,刪除的規(guī)則是如果該評論下還有回復,也一并刪除。需要的可以參考一下
    2022-01-01
  • 分析ABA問題的本質及其解決辦法

    分析ABA問題的本質及其解決辦法

    CAS的全稱是compare and swap,它是java同步類的基礎,java.util.concurrent中的同步類基本上都是使用CAS來實現其原子性的。本文將介紹ABA問題的本質及其解決辦法。
    2021-06-06
  • 新手了解java 集合基礎知識

    新手了解java 集合基礎知識

    今天小編就為大家分享一篇關于Java集合總結,小編覺得內容挺不錯的,現在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧,希望對你有所幫助
    2021-07-07

最新評論