Java Timer Source Parsing (Timer Source Parsing)

Keywords: Mobile less

Timer overview

Timer, as its name implies, is a timer for tasks that need to be delayed. The delay time may be 1s or 5 days.
The general usage is as follows:

        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                Log.d("test", "timer task test");
            }
        };
        Timer timer = new Timer();
        timer.schedule(task, new Date(), 1000);

Initialization

From the above code, the main class is Timer. Let's first look at the initialization of Timer:

    public Timer() {
        this("Timer-" + serialNumber());
    }
    public Timer(String name) {
        thread.setName(name);
        thread.start();
    }
    private final TimerThread thread = new TimerThread(queue);

From the above code, we can see that Timer initialization is actually the start of the thread TimerThread, so let's take a look at what this thread does:

public void run() {
        try {
            mainLoop();
        } finally {
            // Someone killed this Thread, behave as if Timer cancelled
            synchronized(queue) {
                newTasksMayBeScheduled = false;
                queue.clear();  // Eliminate obsolete references
            }
        }
    }
    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) {
            }
        }
    }

The code is not long and easy to understand:
1. Check if the task queue is empty and block if it is empty.
2. Get the least waiting time in the queue to see if its status has been closed and whether it needs to be repeated.
3. If the task is not cancelled, there is no need to remove the queue. Then determine whether the time it needs to run is less than or equal to the current time. If it is less than or equal, wait for the time to execute through wait. If it is larger, run directly.

Task queue (small root heap)

The timer will trigger the task with the smallest time each time. This extreme value is very suitable for heap.
The use of heap structure is mainly fast in addition and deletion, and its performance is log(n).
That is to say, adding and deleting operations in the heap of 1000 tasks can be completed within 10 comparisons.

The source code is as follows:

class TaskQueue {

    private TimerTask[] queue = new TimerTask[128];
    private int size = 0;

    int size() {
        return size;
    }

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

    TimerTask getMin() {
        return queue[1];
    }

    TimerTask get(int i) {
        return queue[i];
    }

    void removeMin() {
        queue[1] = queue[size];
        queue[size--] = null;  // Drop extra reference to prevent memory leak
        fixDown(1);
    }

    void quickRemove(int i) {
        assert i <= size;

        queue[i] = queue[size];
        queue[size--] = null;  // Drop extra ref to prevent memory leak
    }

    void rescheduleMin(long newTime) {
        queue[1].nextExecutionTime = newTime;
        fixDown(1);
    }

    boolean isEmpty() {
        return size==0;
    }

    void clear() {
        // Null out task references to prevent memory leak
        for (int i=1; i<=size; i++)
            queue[i] = null;

        size = 0;
    }

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

    private void fixDown(int k) {
        int j;
        while ((j = k << 1) <= size && j > 0) {
            if (j < size &&
                queue[j].nextExecutionTime > queue[j+1].nextExecutionTime)
                j++; // j indexes smallest kid
            if (queue[k].nextExecutionTime <= queue[j].nextExecutionTime)
                break;
            TimerTask tmp = queue[j];  queue[j] = queue[k]; queue[k] = tmp;
            k = j;
        }
    }
    void heapify() {
        for (int i = size/2; i >= 1; i--)
            fixDown(i);
    }
}

The heap implementation in Timer source code is very basic. There are mainly two methods, fixUp() and fixDown(), that is, rising and sinking.
fixUp: In order to ensure heap structure, it is used in addTask.
Fix Down: When the minimum value is taken out, the last node of the heap is placed on the top of the heap, and then the sinking operation is performed to continue to ensure the heap structure, and the minimum value of the remaining value in the heap is placed on the top of the heap.

Posted by slightlyeskew on Mon, 28 Jan 2019 22:21:14 -0800