1. 程式人生 > 其它 >Timer 原理 到 spring 定時器任務

Timer 原理 到 spring 定時器任務

Java Timer 怎麼實現延時任務的?怎麼實現週期任務的?消耗資源多嗎?執行時間準確嗎?

1 Timer 的基本使用。

  

  第一個引數是任務,第二個引數可以是指定時間,第三個引數如果指定了就會週期的執行這個任務

2 Timer 的原理

  概述:Timer 有一個內部執行緒,和一個阻塞佇列,在Timer 執行下一個任務之前會wait指定時間,在Timer 裡面阻塞佇列是空的時候會無限期wait,然後再再新加入任務(2分查詢找到插入位置)的以後彈出需要最近執行的一個任務,並且喚醒阻塞的迴圈消費任務的執行緒。在迴圈消費任務的執行緒中,如果任務是週期任務那麼就計算出下一個執行時間點,然後把它插入到按照時間排序的佇列中(插入過程使用2分查詢),然後判斷當前任務是否到了啟用時間,如果到了就執行,如果沒到就睡到指定時間。

原始碼解析:

  1 新增任務以後判斷了延時時間不能是負數,然後呼叫 sched

  2 sched 方法,做了一些判斷斷,然後拿到任務佇列的鎖,然後阻塞用佇列的新增方法,新增完成以後,判斷如果當前新加入的任務是排最前面的,那麼就喚醒任務執行緒。

    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();
        }
    }

  

 3 阻塞佇列的新增方法,判斷是否需要拓容,通過二分查詢下一次執行時間,找應該插入的位置

    void add(TimerTask task) {
        // Grow backing store if necessary
        if (size + 1 == queue.length)
            queue = Arrays.copyOf(queue, 2*queue.length);

        queue[++size] = task;
        fixUp(size);
    }
    private void fixUp(int k) {
        while (k > 1) {
            int j = k >> 1;
            if (queue[j].nextExecutionTime <= queue[k].nextExecutionTime)
                break;
            TimerTask tmp = queue[j];  queue[j] = queue[k]; queue[k] = tmp;
            k = j;
        }
    }

  

 4迴圈任務,迴圈過程,如果任務佇列是空就等著(直到新增元素後喚醒它),醒了 以後取出最上面的任務,獲取任務鎖,如果任務取消,直接下跳過,比對當前時間和執行時間,判斷任務是否啟用,沒啟用就等到需要啟用的時間,

  如果激活了,如果是普通任務,直接從隊裡裡面刪除,修改執行狀態,如果是迴圈任務,那麼計算出下次執行時間,並且在佇列裡面重排序這個任務,最後呼叫這個任務 run 方法。

    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)) {
                            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);
                            }
                        }
                    }
                    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) {
            }
        }
    }

  

3 什麼情況下Timer  應該避免怎麼用

  因為一個Timer 裡面有一個 執行緒,如果我們有多個延時任務,多種不同週期的任務時,我們應該考慮使用少量或者一個TImer,而不是每種甚至每個任務都建立一個Timer,避免大量資源浪費在多執行緒上下文切換隻上。

4 spring   @Scheduled 的定時任務和 實現了 SchedulingConfigurer 的 定時任務

  他們都支援 cron ,這種定時器原理其實類似於Timer 週期執行,在執行當前任務的時候通過 cron 計算出 下一次執行的時間,然後插入到 任務佇列的指定位置就行了。使用 SchedulingConfigurer自定義定人任務的時候同樣 要避免太過大量的建立 SchedulingConfigurer 的個數,避免太多執行緒上下文切換帶來的消耗。