Java定時器

定時器

開發環境:JDK1.7

需求

1)每隔一段時間來完成某一特定的事

2)參數:(未定類型 時間,String 方法名,Map 參數)

思考:沒有執行類,那麼如何執行操作呢,只傳個方法名和參數有卵用

需求改動

2)–>2.1)增加字節碼參數 Class 操作類

再次思考:用Map來裝載參數合適嗎?我可以通過反射來獲取形參的名字嗎?

百度中…

1.http://www.cnblogs.com/guangshan/p/4660564.html

該方法工作量太大了,所以拒絕使用,但有空來學習一下也不錯

繼續百度中…

2.http://blog.csdn.net/revitalizing/article/details/71036970

天啊!是JDK8的…無奈…

繼續百度中…

3.好吧,事實證明只通過JDK1.7的API是獲取不了參數名的,所以不使用Map來存儲參數了,改爲數組來存儲參數吧..那麼要求傳過來的參數的順序與方法定義的順序相同就行了…

此時的參數爲:

(未定類型 時間,Class 操作類, String 方法名,Object[] 參數)

定時器怎麼定義

查看一下現存代碼的定時器是怎麼搞的

//0.定義一個執行操作的任務器
class MyTask extends TimerTask {
    @Override
    public void run() {//任務操作}
}

//1.先搞一個定時器
Timer timer = new Timer();

//2.調用,開始計時調用吧
    //第一個參數,執行操作的類TimerTask
    //第二個參數,延遲時間,單位毫秒
    //第三個參數,執行時間間隔,單位毫秒
//該方法有有多個重載方法,詳情參考API
timer.schedule(new MyTask(), 0, 1000);

問題又來了,參數怎麼傳進任務器裏?

1)在TimerTask定義一個public方法,然後再run方法裏調用public方法

2)通過回調機制來實現參數的傳遞,Thank GOD!聽上去非常高大上!

實現細節是這樣的:自己百度去:)

不用該方法,有簡單的不用,幹嘛呢

如果使用回調機制我還需要寫個接口,通過接口裏方法所返回的值來獲取數據,這麼的方式明顯不是一個好方法,所以就不用了

問題又來了,該在哪裏啓動定時器的schedule方法呢 ?

1)單獨建立一個類來作爲工具類,裏面的public方法可以讓使用者來調用來啓動任務,當然,需要傳參進來,我再把參數傳到TimerTask

2)在TimerTask的public方法啓動,把所存參數存到屬性裏,在run方法裏實現操作,而不再run裏調用public方法裏,或者再寫一個private方法,在private方法裏實現操作,再在run方法裏調用private方法.

由於我的定時器的操作是單一的,所以就不用再創建一個private方法來封裝操作代碼了.

暫時定時器工具類的框架就這麼定了

demo

public class MyTimerUtil extends TimerTask {

    @Override
    public void run() {
        System.out.println("hello timer!");
    }

    public <T> Object invokeTimer(long millisecond, Class<T> clazz, String mehtodName, Object... params) {

        Timer timer = new Timer();
        timer.schedule(this, 0, millisecond);
        return null;
    }

    public static void main(String[] args) {
        MyTimerUtil myTimerUtil = new MyTimerUtil();
        Object myResult = myTimerUtil.invokeTimer(1000L, null, null);
        System.out.println(myResult);
    }

}

總體框架demo

public class MyTimerUtil extends TimerTask {

    private Method method;
    private Object[] params;
    private Object instance;

    @Override
    public void run() {

        try {
            method.invoke(instance, params);
            System.out.println("hello method");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public <T> void invokeTimer(long millisecond, Class<T> clazz, String mehtodName, Object... params) throws Exception {

        method = clazz.getMethod(mehtodName);
        this.params = params;
        instance = clazz.newInstance();
        Timer timer = new Timer();
        timer.schedule(this, 0, millisecond);
    }

    public static void main(String[] args) throws Exception {

        MyTimerUtil myTimerUtil = new MyTimerUtil();
        myTimerUtil.invokeTimer(1000L, MyTimerUtil.class, "testMethod");

    }

    public void testMethod() {
        System.out.println("in testMethod");
    }

}

還有什麼更好地辦法實現定時器呢?

百度中…

http://blog.csdn.net/haorengoodman/article/details/23281343/

使用ScheduledExecutorService方法,接下來會在下面寫個demo

使用這個的優勢是:(這是從上面的連接copy下來的)

  • ScheduledExecutorService是從Java SE5的java.util.concurrent裏,做爲併發工具類被引進的,這是最理想的定時任務實現方式。

  • 相比於上兩個方法,它有以下好處:

  • 1>相比於Timer的單線程,它是通過線程池的方式來執行任務的

  • 2>可以很靈活的去設定第一次執行任務delay時間
  • 3>提供了良好的約定,以便設定執行的時間間隔

demo

public class MyScheduledExecutorUtil implements Runnable{
    private Method method;
    private Object[] params;
    private Object instance;

    public <T> void invokeTimer(int second, Class<T> clazz, String mehtodName, Object... params) throws Exception {

        method = clazz.getMethod(mehtodName);
        this.params = params;
        instance = clazz.newInstance();
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);//線程池的大小,允許的最大線程數
        scheduledExecutorService.scheduleAtFixedRate(this, 0, second, TimeUnit.SECONDS);
    }

    @Override
    public void run() {
        try {
            method.invoke(instance, params);
            System.out.println("hello method");
        } catch (Exception e) {
            e.printStackTrace();
        }   
    }

    public void testMethod() {
        System.out.println("in testMethod");
    }

    public static void main(String[] args) throws Exception {

        MyScheduledExecutorUtil myTimerUtil = new MyScheduledExecutorUtil();
        myTimerUtil.invokeTimer(1, MyTimerUtil.class, "testMethod");

    }
}

再次提醒

本項目使用目錄的"總體框架demo"作爲工具類模板開發,以下簡稱該工具類模板爲"工具類"

怎麼停止定時器?

Timer有個cancel方法,可以停止定時器

TimerTask也有個cancel方法,可以取消該TimerTask任務,
我們不是timer.schedule(TimerTask, delay, period);定時執行TimerTask任務的嗎?

他們的區別是Timer的cancel是把整個定時器都退出了,而TimerTask的是把任務停止了,但定時器還在工作,儘管定時器裏已經沒有任務了,定時器還是在監聽着,耗費着資源

我們的需求就是要停止定時器,所以調用Timer的cancel.

把Timer定義爲靜態屬性

原因:
    1)Timer爲靜態屬性,可以被所有"工具類實例"共享
    2)每一個實例實質是一個TimerTask,每次創建工具類實例並執行invokeTimer方法,相當於使用同一個Timer開啓一個線程來運行TimerTask任務,每運行一個TimerTask去創建一個Timer實例就太浪費資源了
    3)若想停止各自的TimerTask,則可以調用工具類的cancel方法,而不影響其他任務
    4)若想關閉Timer,則可以使用Timer的cancel方法,當然,該方法封裝在工具類且聲明爲public以供調用
    *5)說了這麼多廢話,現在纔是真正目的:方便關閉Timer

    若Timer不聲明爲靜態,那麼我想關閉Timer的時候,我先要找出工具類的實列,再通過工具類的實例的已封裝的關閉方法去關閉.

    問題來了,我作爲一個工具類去供人使用,我哪知道他人在哪創建了實例,若我想在非創建實例的類裏去關閉Timer,它都不是在我這個類裏創建的,那我如何獲取該實例呢去關閉Timer呢?

    若Timer聲明爲靜態屬性那麼就好辦了,直接"類名.關閉方法",哈哈哈,就關閉了,多麼的方便..

    若真的只想關閉使用者自己的任務而不影響其他人的任務,那就簡單了,"實例.cancel()"就可以來,我都說了,我們的工具類實質是一個TimerTask,其本來就有一個取消任務所用的方法cancel,至於如何在非創建實例的類獲取實例的問題,我可就不管了,最起碼解決了關閉Timer的問題

問題又來了,反射獲取方法需要參數類型

解決有兩種方法:

  1. 通過調用者傳進的參數獲取其Class字節碼,但是如果其傳進的爲基本類型那麼就崩了,因爲通過Object[]來獲取參數時,會自動打包,如int會打包爲Integer,此時類型不匹配,找不到方法,(PS:我測試過許多遍了)所以就不用該方法了
  2. 就讓調用者傳遞那參數類型的字節碼過來,想用就要按照規定的來.

發現東西了,可變參數

可變參數若只傳null,則會爲null

public static void main(String[] args) throws Exception {       
    test(null);
}

private static void test(Object... a) {
    System.out.println(a);
}   

輸出:null

傳多個null.則會不爲null

public static void main(String[] args) throws Exception {
    test(null,null);
}

private static void test(Object... a) {
    System.out.println(a+" length="+a.length+" a[1]="+a[1]);
}   
輸出:[Ljava.lang.Object;@45486b51 length=2 a[1]=null

不傳時,不爲null,其數組大小爲0

public static void main(String[] args) throws Exception {           
    test();
}

private static void test(Object... a) {
    System.out.println(a+" length="+a.length);
}
輸出:[Ljava.lang.Object;@2a97cec length=0

又發現問題了,new Timer()後就會開始監聽

那麼靜態變量何時加載:百度有個說法:

在第一次創建一個類的對象或者第一次調用一個類的靜態屬性和方法的時候,會發生類加載

類加載期間,如果發現有靜態屬性,就給對應的靜態屬性分配內存空間,並賦值

問題在現,Timer的cancel()問題

Timer執行cancel()方法後Timer不會置爲null,但是該對象已經不能再用了

那麼如何判斷Timer是否被cancel了?

1)cancel後將Timer置爲空

2)Timer cancel後再去cancel會拋異常,可以通過捕獲異常來判斷是否已經被cancel了

解決cancel問題和靜態Timer加載後監聽問題

1)在封裝的cancel方法將Timer置爲空

public void cancelTimer() {
    if(timer!=null) {
        timer.cancel();
        timer = null;
    }
}

2)在構造方法裏new Timer(),避免任何第一次加載工具類時都觸發new Timer 從而監聽,浪費資源

public TimerUtils() {
    if(timer == null) {
        timer = new Timer();
    }
}

最終的結果如下

import java.lang.reflect.Method;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;


/**
 * 1.定時器工具類,定時器Timer爲static,所有實列共享<br>
 * 2.該工具類提供了cancelTimer()方法,以終止定時器的運行<br>
 * 3.該工具類本身繼承了TimerTask類,故若想終止某一實例所開啓的任務,可以直接調用繼承方法cancel(),只終止任務,不終止定時器
 */
public class TimerUtils extends TimerTask {

    private Method method;
    private Object[] params;
    private Object instance;
    private static Timer timer;//全部實例共享的定時器

    /**
     * Timer在構造方法裏實例化
     */
    public TimerUtils() {
        if(timer == null) {
            timer = new Timer();
        }
    }

    @Override
    public void run() {

        try {
            method.invoke(instance, params);
        } catch (Exception e) {
            e.printStackTrace();
            this.cancel();//調用方法失敗,取消該任務,但不終止Timer,Timer可能存在有其他任務
        }
    }

    /**
     * 反射獲取方法
     * @param clazz 被調用方法的類的字節碼,用來獲取Method實例
     * @param mehtodName 方法名
     * @param paramTypes 參數類型,注意:基本類型與其包裝類是有區別的,如:int與Integer
     * @param params 參數
     * @throws NoSuchMethodException
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    private void initMehtod(Class<?> clazz, String mehtodName, Class<?>[] paramTypes,
            Object... params) throws Exception {

        if(!isAccessableMehtod(clazz, mehtodName, paramTypes, params)) {
            throw new Exception("所傳參數不符合反射獲取方法或者調用方法的最低條件");
        }
        try {   
            method = clazz.getMethod(mehtodName, paramTypes); 
        } catch (NoSuchMethodException e) {
            method = clazz.getDeclaredMethod(mehtodName, paramTypes);
            method.setAccessible(true); //獲取方法的訪問權限
        }
        this.params = params;
        instance = clazz.newInstance();

    }

    /**
     * 判斷參數是否滿足反射獲取方法或者調用方法的最低條件
     * @param clazz 被調用方法的類的字節碼,用來獲取Method實例
     * @param mehtodName 方法名
     * @param paramTypes 參數類型,注意:基本類型與其包裝類是有區別的,如:int與Integer
     * @param params 參數
     * @return
     */
    private boolean isAccessableMehtod(Class<?> clazz, String mehtodName, Class<?>[] paramTypes, Object... params) {
        paramTypes = paramTypes==null || paramTypes.length==0 ? null : paramTypes;
        params = params==null || params.length==0 ? null : params;
        if(clazz==null || StringUtil.isEmpty(mehtodName))
            return false;
        if(paramTypes==params || (paramTypes!=null && params!=null && (paramTypes.length == params.length)))
            return true;
        return false;
    }

    /**
     * @param millisDelay 首次執行的延遲時間,單位:毫秒
     * @param millisPeriod 週期,上一次與下一次執行的時間間隔,單位:毫秒
     * @param clazz 執行操作的類的字節碼
     * @param mehtodName 執行操作的方法名
     * @param paramTypes 參數類型,注意:基本類型與其包裝類是有區別的,如:int與Integer
     * @param params 參數
     * @throws Exception
     */
    public void invokeTimer(long millisDelay, long millisPeriod, Class<?> clazz, String mehtodName, Class<?>[] paramTypes, Object... params) throws Exception {
        initMehtod(clazz, mehtodName, paramTypes, params);
        timer.schedule(this, millisDelay, millisPeriod);
    }

    /**
     * @param firstTime 首次執行的時間,若時間已過則立即執行
     * @param millisPeriod 週期,上一次與下一次執行的時間間隔,單位:毫秒
     * @param clazz 執行操作的類的字節碼
     * @param mehtodName 執行操作的方法名
     * @param paramTypes 參數類型,注意:基本類型與其包裝類是有區別的,如:int與Integer
     * @param params 參數
     * @throws Exception
     */
    public void invokeTimer(Date firstTime, long millisPeriod, Class<?> clazz, String mehtodName, Class<?>[] paramTypes, Object... params) throws Exception {
        initMehtod(clazz, mehtodName, paramTypes, params);
        timer.schedule(this, firstTime, millisPeriod);
    }

    /**
     * 終止定時器
     */
    public static void cancelTimer() {
        if(timer!=null) {
            timer.cancel();
            timer = null;
        }
    }

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章