天天看點

JAVA定時器使用與原理分析

如何使用

首先定義一個類,繼承TimerTask

static class MyTimerTask extends TimerTask {

    private final MyWebSocketClient client;

    public MyTimerTask(MyWebSocketClient client) {
        this.client = client;
    }

    @Override
    public void run() {
        System.out.print("任務執行");
    }
}      

配置MyTimerTask的執行政策

Timer timer = new Timer();
MyTimerTask timerTask = new MyTimerTask(client);
timer.schedule(timerTask, 1000, 2000);      

上訴執行政策的意思為:從現在起過1000毫秒以後,每隔2000毫秒執行一次。

除了支援這種模式的政策設定還有很多

// time為Date類型:在指定時間執行一次。
timer.schedule(task, time);

// firstTime為Date類型,period為long
// 從firstTime時刻開始,每隔period毫秒執行一次。
timer.schedule(task, firstTime, period);

// delay 為long類型:從現在起過delay毫秒執行一次
timer.schedule(task, delay)

// delay為long,period為long:從現在起過delay毫秒以後,每隔period
// 毫秒執行一次。  
timer.schedule(task, delay, period)      

原理分析

timer底層是把一個個任務放在一個TaskQueue中,TaskQueue是以平衡二進制堆表示的優先級隊列,他是通過nextExecutionTime進行優先級排序的,距離下次執行時間越短優先級越高,通過getMin()獲得queue[1]

并且出隊的時候通過synchronized保證線程安全,延遲執行和特定時間執行的底層實作類似,源碼如下

private void sched(TimerTask task, long time, long period) {
    if (time < 0)
        throw new IllegalArgumentException("Illegal execution time.");

    // Constrain value of period sufficiently to prevent numeric
    // overflow while still being effectively infinitely large.
    if (Math.abs(period) > (Long.MAX_VALUE >> 1))
        period >>= 1;

    synchronized(queue) {
        if (!thread.newTasksMayBeScheduled)
            throw new IllegalStateException("Timer already cancelled.");

        synchronized(task.lock) {
            if (task.state != TimerTask.VIRGIN)
                throw new IllegalStateException(
                    "Task already scheduled or cancelled");
            task.nextExecutionTime = time;
            task.period = period;
            task.state = TimerTask.SCHEDULED;
        }

        queue.add(task);
        if (queue.getMin() == task) // 如果目前任務處于隊列的第一個說明輪到這個任務執行了
            queue.notify();
    }
}      

我們主要來看下周期性排程通過什麼方式實作的,我們直接來分析源碼如下:

private void mainLoop() {
    // 首先一直監聽隊列中有沒有任務
    while (true) {
        try {
            TimerTask task;
            boolean taskFired;
            // 同步,保證任務執行順序
            synchronized (queue) {
                // Wait for queue to become non-empty
                while (queue.isEmpty() && newTasksMayBeScheduled)
                    queue.wait();
                if (queue.isEmpty())
                    break; // Queue is empty and will forever remain; die

                // Queue nonempty; look at first evt and do the right thing
                long currentTime, executionTime;
                // 擷取優先級最高的任務
                task = queue.getMin();
                synchronized (task.lock) {
                    if (task.state == TimerTask.CANCELLED) {
                        queue.removeMin();
                        continue; // No action required, poll queue again
                    }
                    currentTime = System.currentTimeMillis();
                    // 擷取任務下次執行時間
                    executionTime = task.nextExecutionTime;
                    if (taskFired = (executionTime <= currentTime)) {
                        // 到這裡是延遲執行和特定時間點執行已經結束了,狀态标記為EXECUTED,周期性執行繼續往下走
                        if (task.period == 0) { // Non-repeating, remove
                            queue.removeMin();
                            task.state = TimerTask.EXECUTED;
                        } else { // Repeating task, reschedule
                            // 這裡他又重新計算了下下個任務的執行,并且任務還在隊列中
                            queue.rescheduleMin(
                                    task.period < 0 ? currentTime - task.period
                                            : executionTime + task.period);
                        }
                    }
                }
                // 如果任務執行時間大于目前時間說明任務還沒點,繼續等,否則執行run代碼塊
                if (!taskFired) // Task hasn't yet fired; wait
                    queue.wait(executionTime - currentTime);
            }
            if (taskFired) // Task fired; run it, holding no locks
                task.run();
        } catch (InterruptedException e) {
        }
    }
}
}      

缺點

1、首先Timer對排程的支援是基于絕對時間的,而不是相對時間,是以它對系統時間的改變非常敏感。

2、其次Timer線程是不會捕獲異常的,如果TimerTask抛出的了未檢查異常則會導緻Timer線程終止,同時Timer也不會重新恢複線程的執行,他會錯誤的認為整個Timer線程都會取消。同時,已經被安排單尚未執行的TimerTask也不會再執行了,新的任務也不能被排程。故如果TimerTask抛出未檢查的異常,Timer将會産生無法預料的行為

3、Timer在執行定時任務時隻會建立一個線程任務,如果存在多個線程,若其中某個線程因為某種原因而導緻線程任務執行時間過長,超過了兩個任務的間隔時間,會導緻下一個任務執行時間滞後