天天看點

定時任務最簡單的3種實作方法(超實用)

定時任務在實際的開發中特别常見,比如電商平台 30 分鐘後自動取消未支付的訂單,以及淩晨的資料彙總和備份等,都需要借助定時任務來實作,那麼我們本文就來看一下定時任務最簡單的幾種實作方式。

Timer 是 JDK 自帶的定時任務執行類,無論任何項目都可以直接使用 Timer 來實作定時任務,是以 Timer 的優點就是使用友善,它的實作代碼如下:

程式執行結果如下:

Run timerTask:Mon Aug 17 21:29:25 CST 2020 Run timerTask:Mon Aug 17 21:29:28 CST 2020 Run timerTask:Mon Aug 17 21:29:31 CST 2020

Timer 類實作定時任務雖然友善,但在使用時需要注意以下問題。

當一個任務的執行時間過長時,會影響其他任務的排程,如下代碼所示:

進入 timerTask 1:Mon Aug 17 21:44:08 CST 2020 Run timerTask 1:Mon Aug 17 21:44:13 CST 2020 Run timerTask 2:Mon Aug 17 21:44:13 CST 2020 進入 timerTask 1:Mon Aug 17 21:44:13 CST 2020 Run timerTask 1:Mon Aug 17 21:44:18 CST 2020 進入 timerTask 1:Mon Aug 17 21:44:18 CST 2020 Run timerTask 1:Mon Aug 17 21:44:23 CST 2020 Run timerTask 2:Mon Aug 17 21:44:23 CST 2020 進入 timerTask 1:Mon Aug 17 21:44:23 CST 2020

從上述結果中可以看出,當任務 1 運作時間超過設定的間隔時間時,任務 2 也會延遲執行。原本任務 1 和任務 2 的執行時間間隔都是 3s,但因為任務 1 執行了 5s,是以任務 2 的執行時間間隔也變成了 10s(和原定時間不符)。

使用 Timer 類實作定時任務時,當一個任務抛出異常,其他任務也會終止運作,如下代碼所示:

進入 timerTask 1:Mon Aug 17 22:02:37 CST 2020 Exception in thread "Timer-0" java.lang.ArithmeticException: / by zero ​ at com.example.MyTimerTask$1.run(MyTimerTask.java:21) ​ at java.util.TimerThread.mainLoop(Timer.java:555) ​ at java.util.TimerThread.run(Timer.java:505) Process finished with exit code 0

Timer 類實作定時任務的優點是友善,因為它是 JDK 自定的定時任務,但缺點是任務如果執行時間太長或者是任務執行異常,會影響其他任務排程,是以在生産環境下建議謹慎使用。

ScheduledExecutorService 也是 JDK 1.5 自帶的 API,我們可以使用它來實作定時任務的功能,也就是說 ScheduledExecutorService 可以實作 Timer 類具備的所有功能,并且它可以解決了 Timer 類存在的所有問題。

ScheduledExecutorService 實作定時任務的代碼示例如下:

Run Schedule:Mon Aug 17 21:44:23 CST 2020 Run Schedule:Mon Aug 17 21:44:26 CST 2020 Run Schedule:Mon Aug 17 21:44:29 CST 2020

ScheduledExecutorService 可以解決 Timer 任務之間相應影響的缺點,首先我們來測試一個任務執行時間過長,會不會對其他任務造成影響,測試代碼如下:

Run Schedule2:Mon Aug 17 11:27:55 CST 2020 進入 Schedule:Mon Aug 17 11:27:55 CST 2020 Run Schedule2:Mon Aug 17 11:27:58 CST 2020 Run Schedule:Mon Aug 17 11:28:00 CST 2020 進入 Schedule:Mon Aug 17 11:28:00 CST 2020 Run Schedule2:Mon Aug 17 11:28:01 CST 2020 Run Schedule2:Mon Aug 17 11:28:04 CST 2020

從上述結果可以看出,當任務 1 執行時間 5s 超過了執行頻率 3s 時,并沒有影響任務 2 的正常執行,是以使用 ScheduledExecutorService 可以避免任務執行時間過長對其他任務造成的影響。

接下來我們來測試一下 ScheduledExecutorService 在一個任務異常時,是否會對其他任務造成影響,測試代碼如下:

進入 Schedule:Mon Aug 17 22:17:37 CST 2020 Run Schedule2:Mon Aug 17 22:17:37 CST 2020 Run Schedule2:Mon Aug 17 22:17:40 CST 2020 Run Schedule2:Mon Aug 17 22:17:43 CST 2020

從上述結果可以看出,當任務 1 出現異常時,并不會影響任務 2 的執行。

在單機生産環境下建議使用 ScheduledExecutorService 來執行定時任務,它是 JDK 1.5 之後自帶的 API,是以使用起來也比較友善,并且使用 ScheduledExecutorService 來執行任務,不會造成任務間的互相影響。

如果使用的是 Spring 或 Spring Boot 架構,可以直接使用 Spring Framework 自帶的定時任務,使用上面兩種定時任務的實作方式,很難實作設定了具體時間的定時任務,比如當我們需要每周五來執行某項任務時,但如果使用 Spring Task 就可輕松的實作此需求。

以 Spring Boot 為例,實作定時任務隻需兩步:

開啟定時任務;

添加定時任務。

具體實作步驟如下。

開啟定時任務隻需要在 Spring Boot 的啟動類上聲明 <code>@EnableScheduling</code> 即可,實作代碼如下:

定時任務的添加隻需要使用 <code>@Scheduled</code> 注解标注即可,如果有多個定時任務可以建立多個 <code>@Scheduled</code> 注解标注的方法,示例代碼如下:

注意:定時任務是自動觸發的無需手動幹預,也就是說 Spring Boot 啟動後會自動加載并執行定時任務。

Spring Task 的實作需要使用 cron 表達式來聲明執行的頻率和規則,cron 表達式是由 6 位或者 7 位組成的(最後一位可以省略),每位之間以空格分隔,每位從左到右代表的含義如下:

其中 * 和 ? 号都表示比對所有的時間。

cron 表達式線上生成位址:https://cron.qqe2.com/

上面的方法都是關于單機定時任務的實作,如果是分布式環境可以使用 Redis 來實作定時任務。

使用 Redis 實作延遲任務的方法大體可分為兩類:通過 ZSet 的方式和鍵空間通知的方式。

通過 ZSet 實作定時任務的思路是,将定時任務存放到 ZSet 集合中,并且将過期時間存儲到 ZSet 的 Score 字段中,然後通過一個無線循環來判斷目前時間内是否有需要執行的定時任務,如果有則進行執行,具體實作代碼如下:

我們可以通過 Redis 的鍵空間通知來實作定時任務,它的實作思路是給所有的定時任務設定一個過期時間,等到了過期之後,我們通過訂閱過期消息就能感覺到定時任務需要被執行了,此時我們執行定時任務即可。

預設情況下 Redis 是不開啟鍵空間通知的,需要我們通過 <code>config set notify-keyspace-events Ex</code> 的指令手動開啟,開啟之後定時任務的代碼如下:

更多關于定時任務的實作,請點選《史上最全的延遲任務實作方式彙總!附代碼》。

關注下面二維碼,訂閱更多精彩内容。

定時任務最簡單的3種實作方法(超實用)
定時任務最簡單的3種實作方法(超實用)
定時任務最簡單的3種實作方法(超實用)

關注公衆号(加好友):

定時任務最簡單的3種實作方法(超實用)

作者:

王磊的部落格

出處:

http://vipstone.cnblogs.com/