亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Java中實(shí)現(xiàn)定時(shí)任務(wù)的兩種方法舉例詳解

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

一、定時(shí)任務(wù)

概念

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

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

以上的種種到達(dá)指定時(shí)間做指定事情,就是定時(shí)任務(wù)。

作用

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

二、簡單定時(shí)任務(wù)實(shí)現(xiàn)方式

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

在Java中,常見的可以實(shí)現(xiàn)定時(shí)任務(wù)的方式有如下幾種:

(1)線程類實(shí)現(xiàn)定時(shí)任務(wù):比如Thread、Runnable、Callable等線程類都可以實(shí)現(xiàn)定時(shí)任務(wù)。

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

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

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

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

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

/**
 * 匿名內(nèi)部類實(shí)現(xiàn) java.lang.Runnable 接口
 */
public class ThreadTask {
    public static void main(String[] args) {
        final long timeInterval = 1000;
 
        //創(chuàng)建線程(匿名內(nèi)部類方式)
        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("線程等待實(shí)現(xiàn)定時(shí)任務(wù):" + 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實(shí)現(xiàn)java.lang.Runnable接口)
        Thread t = new Thread(runnable);
        //開啟線程
        t.start();
    }
}
 
/**
 * 自定義類MyRunnable實(shí)現(xiàn)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("線程等待實(shí)現(xiàn)定時(shí)任務(wù)1:" + dateStr);
 
            try {
                Thread.sleep(timeInterval);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

2. 使用java.util.Timer

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

Timer類核心方法如下:

// 在指定延遲時(shí)間后執(zhí)行指定的任務(wù)
schedule(TimerTask task,long delay);
 
// 在指定時(shí)間執(zhí)行指定的任務(wù)。(只執(zhí)行一次)
schedule(TimerTask task, Date time);
 
// 延遲指定時(shí)間(delay)之后,開始以指定的間隔(period)重復(fù)執(zhí)行指定的任務(wù)
schedule(TimerTask task,long delay,long period);
 
// 在指定的時(shí)間開始按照指定的間隔(period)重復(fù)執(zhí)行指定的任務(wù)
schedule(TimerTask task, Date firstTime , long period);
 
// 在指定的時(shí)間開始進(jìn)行重復(fù)的固定速率執(zhí)行任務(wù)
scheduleAtFixedRate(TimerTask task,Date firstTime,long period);
 
// 在指定的延遲后開始進(jìn)行重復(fù)的固定速率執(zhí)行任務(wù)
scheduleAtFixedRate(TimerTask task,long delay,long period);
 
// 終止此計(jì)時(shí)器,丟棄所有當(dāng)前已安排的任務(wù)。
cancal();
 
// 從此計(jì)時(shí)器的任務(wù)隊(duì)列中移除所有已取消的任務(wù)。
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();
        // 安排任務(wù)在1秒后執(zhí)行,并且每隔1秒執(zhí)行一次
        timer.scheduleAtFixedRate(task, 1000, 1000);
    }
}

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

Timer 優(yōu)缺點(diǎn)分析

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

缺點(diǎn):

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

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

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

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

(3)任務(wù)的無法持久化

當(dāng)應(yīng)用程序關(guān)閉或重啟時(shí),Timer 中已經(jīng)調(diào)度的任務(wù)會丟失。

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

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

當(dāng)一個(gè)任務(wù)的執(zhí)行時(shí)間過長時(shí),會影響其他任務(wù)的調(diào)度。

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) {
        // 定時(shí)任務(wù)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("進(jìn)入定時(shí)任務(wù)1:" + dateStr);
                // 休眠5秒
                try {TimeUnit.SECONDS.sleep(5);}
                catch (InterruptedException e) {e.printStackTrace();}
                dateStr = sdf.format(new Date());
                System.out.println("運(yùn)行定時(shí)任務(wù)1:" + dateStr);
            }
        };

        // 定時(shí)任務(wù)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("-----進(jìn)入定時(shí)任務(wù)2:" + dateStr);
                dateStr = sdf.format(new Date());
                System.out.println("-----運(yùn)行定時(shí)任務(wù)2:" + dateStr);
            }
        };

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

這段代碼展示了如何使用Java的TimerTimerTask類來實(shí)現(xiàn)定時(shí)任務(wù)的調(diào)度。以下是對代碼的分析:

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

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

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

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

任務(wù)調(diào)度的具體過程:

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

當(dāng)任務(wù) 1 運(yùn)行時(shí)間超過設(shè)定的間隔時(shí)間時(shí),任務(wù) 2 也會延遲執(zhí)行。 原本任務(wù) 1 和任務(wù) 2 的執(zhí)行時(shí)間間隔都是 2s,但因?yàn)槿蝿?wù) 1 執(zhí)行了 5s,因此任務(wù) 2 的執(zhí)行時(shí)間間隔也變成了10秒(和原定時(shí)間不符)。

(5)錯(cuò)誤處理能力有限

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

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

使用 Timer 類實(shí)現(xiàn)定時(shí)任務(wù)時(shí),當(dāng)一個(gè)任務(wù)拋出異常,其他任務(wù)也會終止運(yùn)行。

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

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) {
        // 定時(shí)任務(wù)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("進(jìn)入定時(shí)任務(wù)1:" + dateStr);
                //發(fā)生異常
                int num = 10 / 0;
                dateStr = sdf.format(new Date());
                System.out.println("運(yùn)行定時(shí)任務(wù)1:" + dateStr);
            }
        };

        // 定時(shí)任務(wù)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("----進(jìn)入定時(shí)任務(wù)2:" + dateStr);
                dateStr = sdf.format(new Date());
                System.out.println("----運(yùn)行定時(shí)任務(wù)2:" + dateStr);
            }
        };
        // 計(jì)時(shí)器
        Timer timer = new Timer();
        // 添加執(zhí)行任務(wù)(延遲 1s 執(zhí)行,每 2s 執(zhí)行一次)
        timer.schedule(timerTask, 1000, 2000);
        timer.schedule(timerTask2, 1000, 2000);
    }
}

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

3. 使用JDK自帶的ScheduledExecutorService

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

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

注意:只有當(dāng)執(zhí)行調(diào)度任務(wù)時(shí),ScheduledExecutorService才會真正啟動一個(gè)線程,其余時(shí)間ScheduledExecutorService都是出于輪詢?nèi)蝿?wù)的狀態(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í)行任務(wù)的時(shí)間:" + dateTime);
            }
        };
        // 安排任務(wù)在1秒后執(zhí)行,并且每隔1秒執(zhí)行一次
        scheduler.scheduleAtFixedRate(task, 1, 1, TimeUnit.SECONDS);
    }
}

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

 schedule和scheduleAtFixedRate的區(qū)別

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

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

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

  • 在任務(wù)執(zhí)行未超時(shí)時(shí),它們都是上次執(zhí)行時(shí)間加上間隔時(shí)間,來執(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是固定延遲,更加側(cè)重保持延遲間隔的固定性。每次都是以上一個(gè)任務(wù)的起始時(shí)間來判斷時(shí)間間隔。

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

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

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

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

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

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

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

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

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

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

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

4. 使用SpringTask實(shí)現(xiàn)定時(shí)任務(wù)

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

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

以 Spring Boot 為例,實(shí)現(xiàn)定時(shí)任務(wù)只需兩步:

  • 開啟定時(shí)任務(wù)
  • 添加定時(shí)任務(wù)

(1)開啟定時(shí)任務(wù)

如果是在Spring Boot項(xiàng)目中,需要在啟動類上添加@EnableScheduling來開啟定時(shí)任務(wù)

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

(2)添加定時(shí)任務(wù)

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

@Component  //@Component用于實(shí)例化類,將其類托管給 Spring 容器
public class TaskJobUtil {
    /**
     * cron表達(dá)式:表示每2秒 執(zhí)行任務(wù)
     */
    @Scheduled(cron = "0/2 * * * * ?")
    public void task() {
        System.out.println("task0-start");
        sleep(5);
        System.out.println("task0-end");
    }
 
    /**
     * fixedRate:每間隔2秒執(zhí)行一次任務(wù)
     * 注意,默認(rèn)情況下定時(shí)任務(wù)是在同一線程同步執(zhí)行的,如果任務(wù)的執(zhí)行時(shí)間(如5秒)大于間隔時(shí)間,則會等待任務(wù)執(zhí)行結(jié)束后直接開始下次任務(wù)
     */
    @Scheduled(fixedRate = 2000)
    public void task0() {
        System.out.println("task0-start");
        sleep(5);
        System.out.println("task0-end");
    }
 
    /**
     * fixedDelay:每次延時(shí)2秒執(zhí)行一次任務(wù)
     * 注意,這里是等待上次任務(wù)執(zhí)行結(jié)束后,再延時(shí)固定時(shí)間后開始下次任務(wù)
     */
    @Scheduled(fixedDelay = 2000)
    public void task1() {
        System.out.println("task1-start");
        sleep(5);
        System.out.println("task1-end");
    }
 
    /**
     * initialDelay:首次任務(wù)啟動的延時(shí)時(shí)間
     */
    @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();
        }
    }
}

三、分布式定時(shí)任務(wù)實(shí)現(xiàn)方式

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

1. Quartz

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

使用Quartz可以開發(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既可以單獨(dú)使用也可以跟spring框架整合使用,在實(shí)際開發(fā)中一般會使用后者。

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

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

(2)Quartz架構(gòu)圖如下:

Quartz主要由以下幾個(gè)核心組件組成:

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

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

(3)Quartz的使用步驟

使用Quartz進(jìn)行定時(shí)任務(wù)調(diào)度通常包括以下步驟:

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

示例:使用Quartz進(jìn)行定時(shí)任務(wù)調(diào)度

以下是一個(gè)使用Quartz進(jìn)行定時(shí)任務(wù)調(diào)度的完整示例:

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

在這個(gè)示例中,HelloJob類實(shí)現(xiàn)了Job接口,定義了任務(wù)的執(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 類該類實(shí)現(xiàn)了 Job 接口。實(shí)現(xiàn)了Quartz 調(diào)度器調(diào)用的核心方法 execute 方法。

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

(2)配置調(diào)度器

在這個(gè)示例中,我們創(chuàng)建了一個(gè)調(diào)度器,并定義了一個(gè)任務(wù)和一個(gè)觸發(fā)器。任務(wù)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)建調(diào)度器工廠
            SchedulerFactory schedulerFactory = new org.quartz.impl.StdSchedulerFactory();
            Scheduler scheduler = schedulerFactory.getScheduler();

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

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

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

在這個(gè)示例中,我們創(chuàng)建一個(gè)調(diào)度器工廠的實(shí)例schedulerFactory,使用默認(rèn)的標(biāo)準(zhǔn)調(diào)度器工廠。從調(diào)度器工廠獲取一個(gè)調(diào)度器實(shí)例scheduler,用于安排和執(zhí)行任務(wù)。

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

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

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

啟動調(diào)度器,使其開始調(diào)度任務(wù)。

(3)使用Cron表達(dá)式

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

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

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

Quartz的持久化

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

(1)配置持久化存儲

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

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

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

(2)數(shù)據(jù)庫表

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

2. XXL-Job

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

3. Elastic-Job

Elastic-Job是一個(gè)開源的分布式任務(wù)調(diào)度解決方案,它是基于Java的輕量級分布式調(diào)度框架。

比較

三者的比較

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

四、總結(jié)

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

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

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

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

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

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

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

相關(guān)文章

最新評論