Java 中的定時任務(一)


定時任務簡單來說就是在指定時間,指定的頻率來執行一個方法,而在 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 的簡化版本……


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM