Java 中的定時任務(一)

coyan發表於2021-09-09

定時任務簡單來說就是在指定時間,指定的頻率來執行一個方法,而在 Java 中我們又該如何實現呢?

 

想來主要有 3 種方式,最原始的方式肯定是開啟一個執行緒,讓它睡一會跑一次睡一會跑一次這也就達到了定頻率的執行 run 方法,我們只需要將業務邏輯寫在 run 方法中即可。這種方式總結就是單個執行緒來執行單個任務。

方式一:建立一個執行緒

 

圖片描述

package com.yu.task;import java.util.Date;public class ThreadTest {    public static void main(String[] args) {        // 設定執行週期
        final long timeInterval = 3000;
        
        Runnable runnable = new Runnable() {            public void run() {                while (true) {
                    System.out.println("Task Run ... " + new Date());                    
                    try {
                        Thread.sleep(timeInterval);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
    }
}

圖片描述

 

第二種方式:使用 JDK 自帶的 API Timer 以及 TaskTimer。 

這種方式和第一種簡單粗暴的方式有什麼區別呢,主要體現在使用 API 可以在指定的時間開始啟動任務,可以延期執行首次任務,同樣也看可以設定一定的時間間隔,但是原理是是一樣的,後臺還是啟動了一個執行緒,應該說是隻有一個執行緒在執行任務,不管我們啟動的 Task 有幾個。所以這也會有問題,比方說一個一個任務沒有執行完成,另一個任務就開始執行了,可能會發生併發問題。還有若是一個任務中報錯,則執行緒就會被停止。

 

圖片描述

package com.yu.task;import java.text.SimpleDateFormat;import java.util.Calendar;import java.util.Date;import java.util.Timer;import java.util.TimerTask;public class MyTask extends TimerTask{    private String name;    
    public MyTask(String name) {        this.name = name;
    }    public String getName() {        return name;
    }    public void setName(String name) {        this.name = name;
    }

    @Override    public void run() {
        SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String format = sf.format(new Date());
        System.out.println("exec MyTask ... 當前時間為:" + format);
        System.out.println(this.name +" 正在執行!" + sf.format(new Date()));
    }    
    public static void main(String[] args) {
        Timer timer = new Timer();
        TimerTask task1 = new MyTask("Tasks 1");
        TimerTask task2 = new MyTask("Tasks 2");
        
        Calendar calendar1 = Calendar.getInstance();
        calendar1.add(Calendar.SECOND, 3);
        Calendar calendar2 = Calendar.getInstance();
        calendar2.add(Calendar.SECOND, 5);
        
        SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String format = sf.format(new Date());
        System.out.println("當前時間為:" + format);
        
        timer.schedule(task1, calendar1.getTime(), 3000L);
        timer.schedule(task2, calendar2.getTime(), 3000L);
    }

}

圖片描述

 

其實在 Timer 中,封裝了一個 Task 的佇列和 Time 的執行緒物件,我們自定義的 Task 的引用會放在佇列中等待執行。

大致是這麼一個關係 Timer -  TimerThread - TaskQueue - MyTask - run  當然最終執行的方法肯定是我們自定義任務中的 run 方法。因為我們自定義的任務已經繼承了 TimeTask ,而這個類已經實現了 Runnable 介面。

 

Timer 定時器第一種方式好的地方還在於可以選擇關閉任務,檢視任務的執行情況等。下面介紹幾個相關的方法。

啟動定時任務也有幾個不同的方法,每個都有不同的使用場景。

圖片描述

上面是 Timer 類中的屬性和方法的簡圖,開啟一個任務我們主要使用 6 個方法, 一共 3 組,先看簡單的

schedule() 方法中含有兩個引數,主要是用來執行一次任務的,不存在頻率的問題,而 3 個參數列示執行什麼任務,什麼時候開始執行/延時多久執行,多久執行一次。

 

現在來假設一個場景,01秒開始執行一個任務,頻率是 3 秒執行一次,不知道你們有沒有想到這個情況,若是這個方法本身執行需要 4 秒,那第二次執行的時間是 04 呢? 還是 05 秒呢?

 

而這個差別就是 schedule 和 scheduleAtFixedRate 方法的區別,前者會按照任務執行的情況來執行下一次任務,也就是說 01 之後,會等第一次執行結束再開始第二次,這樣就會帶來一個問題,每次執行時間都比預想的要晚。而 scheduleAtFixedRate 則不存在這個問題,它會按照指定的時間和頻率執行,這樣坐就會出現,同一時刻會有兩個任務在同時執行,若是,可能會發生併發問題。

 

上面討論的是當任務本身執行的時間大於頻率時,兩個方法不同的執行情況,還有一個情況,若是當前時間晚於我們設定的開始執行時間又會怎麼辦呢?

 

schedule () 會比較符合正常思維,晚了就晚了,現在開始執行就是,不存在補回的情況,但是 scheduleAtFixedRate 則會一次性補回未執行的次數。舉例來說,當前時間為 09,而我們設定的開始時間為 00 ,頻率為 3 秒,則 schedule 會在 09 執行一次,12 執行一次。而 scheduleAtFixedRate 會在 09 一次性執行 4 次,12 執行一次,後面就是正常的頻率。

 

下面再說幾個可能會起到錦上添花作用的方法,我們的若是想取消任務的執行,有一個方法但是分為兩個類執行,我們可以呼叫 TimeTask 中的 cancel 方法,這個方法只對當前任務有效,若是想取消全部的任務,則需要呼叫 Timer 中的 cancel 方法。

 

那有該如何檢視定時器 Timer 中已經被取消任務的數量呢?當然還是有方法的,那就是 Timer 中的 purge 方法。

 

 說了這麼說,實際上我們可以看到,Timer 定時器本身還是呼叫執行緒來完成定時操作。且後臺只有一個多執行緒 TimeThread 在工作。

 

那 Timer 定時器有什麼缺點呢?

1 併發操作時的缺陷,這是因為 Timer 的後臺只有一個執行執行緒導致的,容易引起併發問題。

2 任務丟擲異常時缺陷。如果 TimeTask 丟擲 RuntimeException,Timer 會停止所有任務的執行。

 

所以以後我們在使用 Timer 定時器的時候要注意,這兩個情況,多工且併發執行的時候不要使用 Timer,複雜任務排程的時候也不要使用 Timer ,因為一個不小心出現異常了,所有任務都卡殼了。但是,Timer 定時器處理一些簡單的定是任務還是非常方便的!比方說我想實現的,定時傳送郵件。用起來就很是方便,因為這是 JDK 自帶的 API 呀!

 

第三種方式:使用 Java 中的專門用於定時任務管理的框架 Quartz 。(還沒學,等等吧……)

 

上面也說了 Timer 定時器的弊端,怎麼辦,據聽說 Quartz 可以解決這些……

 

後記:據我知道,Java 中定時任務也就這幾個吧,歡迎補充,有好多人說到某某框架中有定時任務,我想說的是,那不是!那是框架整合了上面說的定時任務框架,可能整合的就是 Quartz,或是 Quartz 的簡化版本……

原文出處:https://www.cnblogs.com/YJK923/p/10075992.html  

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2236/viewspace-2819291/,如需轉載,請註明出處,否則將追究法律責任。

相關文章