An overview of the

Those of you who have used the Timer function in Java development will be familiar with Timer. However, in addition to the Timer, after Java 5 introduces a timing tool ScheduledThreadPoolExecutor, then how should we choose between these two timing tool?

In general we recommend the use of ScheduledThreadPoolExecutor, rather than the Timer, the main reason has the following three points:

  1. Timer uses absolute time, and the change of system time will have certain influence on Timer. And ScheduledThreadPoolExecutor is using a relative time, so there will be no this problem.
  2. Timer using single thread to handle tasks, long-running tasks can lead to delay treatment, other tasks while ScheduledThreadPoolExecutor can customize the number of threads.
  3. Once the Timer is not to deal with a runtime exception, a task trigger a runtime exception, will lead to the collapse of the Timer, but ScheduledThreadPoolExecutor a runtime exception to do capture (can be inafterExecute()Callback method), so it is more secure.

Below we through understanding the operation of the Timer and ScheduledThreadPoolExecutor principles to understand the reason of the above a few problems.

The operation mechanism of Timer

  • TimerTask: Task class. Internal holdnextExecutionTimeVariable, which represents the actual execution time of the task, in millisecondsSystem.currentTimeMillis() + delayThat’s calculated.
  • TimerQueue: priority queue implemented using a small root heap. In accordance with theTimerTaskIn order of actual execution time.
  • TimerThread: As the name suggests, this is the thread that actually performs the task.

TimerThread starts after Timer initialization and then enters the mainLoop() method, which keeps pulling timerTasks from TimerQueue with the smallest time points. If the TimerTask’s execution time is up, timertask.run () is called. Otherwise, call the wait() method and wait for the appropriate time.

By calling timer.schedule (), we actually queue the TimerTask with the timerQueue.add () method.

Here’s another thing to note: The TimerThread is called notify() to wake up the TimerThread when the task has the smallest execution time in the priority queue. The TimerThread then calls timerQueue.getmin () to wait() again. This time, however, the waiting time becomes the point at which new tasks are added.

ScheduledThreadPoolExecutor operation mechanism

ScheduledThreadPoolExecutor inherited from ThreadPoolExecutor, do not understand the principle of the thread pool of students, can have a look at this article: from zero to achieve ImageLoader (three) – the thread pool, rounding.

ScheduledThreadPoolExecutor implementation are more complex than the Timer, but if only to understand the operation principle of the thread pool, actually not difficult also. It simply uses a custom blocking queue, DelayedWorkQueue, to implement task timing on top of ThreadPoolExecutor. So ScheduledThreadPoolExecutor running processes and actually ThreadPoolExecutor is similar.

  • ScheduledFutureTask: Task class. Internal holdtimeThe variable, in nanoseconds, passesSystem.nanoTime() + delayThat’s calculated.
  • DelayedWorkQueue: priority blocking queue implemented using small root heap, willScheduledFutureTaskIn order from smallest to largest, at the same timetake()Method to implement blocking operations.
  • WorkerThreadFor simplicity, I have written the core and temporary threads of the thread pool togetherWorkerThread, but it is important to note ScheduledThreadPoolExecutor is a subclass of the thread pool, so that a thread pool things there are in the ScheduledThreadPoolExecutor.

Light from the point of view of the two figure, like ScheduledThreadPoolExecutor and the realization of the Timer are the same, but is in some names, but in fact the implementation of the two still have a lot of different, Not only because ScheduledThreadPoolExecutor use multithreading.

In the Timer timing function realization rely mainly on TimerThread. The mainLoop () of waiting, and ScheduledThreadPoolExecutor use multithreading, separate implementation timing function in each thread is not realistic, as a result, ScheduledThreadPoolExecutor put the timing function on DelayedWorkQueue class, as DelayedWorkQueue is blocking queue, so the timing task actually realized in DelayedWorkQueue. Take () method. Let’s take a look at what delayedworkqueue.take () does.

The Leader/Follower model

In multithreaded network programming, we generally use one thread to listen on the port, and then use other threads to complete the operation after receiving the event. In this case, the overhead of context switching between the two threads is actually quite high, so we have the Leader/Follower mode:

In the Leader/Follower mode, there is no single thread dedicated to listening, all threads are equivalent, and these threads constantly switch back and forth between the Leader, Follower and Processor states.

The program ensures that there is only one Leader at any given time, and that the Leader temporarily acts as a listener for the port thread. When a new event occurs, instead of finding another thread to process the connection, the Leader converts the event to the Processor and re-assigns a Follower as the new Leader. After the event is processed, the Processor changes to Follower and then becomes the Leader again.

How the take() method works

The take() method here makes use of the idea of Leader/Follower mode, and there is only one Leader thread at a time. However, since the time point of task execution has been determined, it is no longer waiting for a trigger event, but for the delay time corresponding to the minimum task. The rest of the Follower threads wait indefinitely until the current Leader reaches a specified time and turns it into a Processor to process the task. In this case, a Follower will wake up as the next Leader. After the Processor processes the task, it rejoins the Follower and waits.

Absolute time versus relative time

Understand the operating mechanism of the Timer and ScheduledThreadPoolExecutor, let’s take a look at the defects of the Timer is going on.

The first is the absolute time versus relative time. Some people may have noticed that both the TimerTask and ScheduledFutureTask are the actual execution points of the storage, but one is milliseconds and the other is nanoseconds. Does the time unit still have an effect on these? It is true that the time unit does not affect the execution of the task, but the trick is in how the time is calculated: System.currentTimemillis () and System.nanotime ().

System.currenttimemillis () is the number of milliseconds between the current time and midnight on January 1, 1970. Here’s what the official document says:

This method can only be used to measure elapsed time and is not related to any other concept of time in the system or clock time. The return value represents the number of nanoseconds counted from a fixed but arbitrary time.

This is the Timer and ScheduledThreadPoolExecutor a is based on the absolute time and the other is based on the cause of the relative time. Let’s write an example to test this:

public static void main(String[] args) {
    System.out.println("Start:\t" + new Date());

    Executors.newSingleThreadScheduledExecutor().schedule(() -> {
        System.out.println("Executor:\t" + new Date());
    }, 60, TimeUnit.SECONDS);

    new Timer().schedule(new TimerTask() {
        @Override
        public void run(a) {
            System.out.println("Timer:\t" + newDate()); }},60000);
}Copy the code

Output:

Start:    Sun Oct 08 10:51:44 CST 2017
Executor:    Sun Oct 08 10:51:41 CST 2017
Timer:    Sun Oct 08 10:52:45 CST 2017Copy the code

Here, I after startup dispatch system clock back a minute later, so the actual start time should be 10:50:44, due to the wait time has nothing to do with the system ScheduledThreadPoolExecutor, so after a minute; The Timer is based on absolute time so it runs at 10:52:45, which is actually two minutes past.

Single thread versus multi-thread

The second drawback of Timer is that because it uses a single thread, tasks that take a long time to execute can have an impact on other tasks.

public static void main(String[] args) {
    System.out.println("Start:\t\t\t" + new Date());

    ScheduledExecutorService service = Executors.newScheduledThreadPool(3);

    service.schedule(() -> {
        System.out.println("The Executor task 1: \ t" + new Date());
        try {
            Thread.sleep(30000);
        } catch(InterruptedException e) { e.printStackTrace(); }},60, TimeUnit.SECONDS);
    service.schedule(() -> {
        System.out.println("The Executor task 2: \ t" + new Date());
        try {
            Thread.sleep(30000);
        } catch(InterruptedException e) { e.printStackTrace(); }},60, TimeUnit.SECONDS);

    Timer timer = new Timer();

    timer.schedule(new TimerTask() {
        @Override
        public void run(a) {
            System.out.println("The Timer task 1: t \ \ t" + new Date());
            try {
                Thread.sleep(30000);
            } catch(InterruptedException e) { e.printStackTrace(); }}},60000);
    timer.schedule(new TimerTask() {
        @Override
        public void run(a) {
            System.out.println("The Timer task 2: t \ \ t" + new Date());
            try {
                Thread.sleep(30000);
            } catch(InterruptedException e) { e.printStackTrace(); }}},60000);
}Copy the code

Output:

Start: Sun Oct 08 11:10:34 CST 2017 Executor Task 1: Sun Oct 08 11:11:34 CST 2017 Executor Task 2: Sun Oct 08 11:11:34 CST 2017 Timer Task 1: Sun Oct 08 11:11:34 CST 2017 Timer task 2: Sun Oct 08 11:12:04 CST 2017Copy the code

Can see the two tasks of ScheduledThreadPoolExecutor after waiting for a minute to perform at the same time; However, task 2 in the Timer was executed after a total of one and a half minutes due to the execution time of task 1 as long as half a minute.

Exception handling

Finally, we take a look at the Timer and ScheduledThreadPoolExecutor handling of abnormal situations:

Timer

No exception is handled internally by the Timer. If a runtime exception occurs during task execution, the entire TimerThread will crash:

public static void main(String[] args) {
    System.out.println("Start:\t\t\t" + new Date());

    Timer timer = new Timer();

    timer.schedule(new TimerTask() {
        @Override
        public void run(a) {
            throw new RuntimeException("The Timer task 1"); }},60000);
    timer.schedule(new TimerTask() {
        @Override
        public void run(a) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("The Timer task 2: t \ \ t" + newDate()); }},60000);
}Copy the code

Output:

Start:            Sun Oct 08 11:53:05 CST 2017
Exception in thread "Timer-0"Java. Lang. RuntimeException: Timer task 1 at the main. The mainThe $1.run(Main.java:32)
    at java.util.TimerThread.mainLoop(Timer.java:555)
    at java.util.TimerThread.run(Timer.java:505)Copy the code

As you can see, a runtime exception thrown by task 1 causes the entire Timer thread to crash and task 2 does not execute.

ScheduledThreadPoolExecutor

Of exception handling is actually in ScheduledThreadPoolExecutor ThreadPoolExecutor class, ThreadPoolExecutor did capture in the task runtime to abnormal, and abnormal into afterExecute () method:

public class ThreadPoolExecutor extends AbstractExecutorService {
    final void runWorker(Worker w) {... Throwable thrown =null;
        try {
            task.run();
        } catch (RuntimeException x) {
            thrown = x; throw x;
        } catch (Error x) {
            thrown = x; throw x;
        } catch (Throwable x) {
            thrown = x; throw new Error(x);
        } finally{ afterExecute(task, thrown); }... }}Copy the code

Let’s verify:

public static void main(String[] args) {
    System.out.println("Start:\t\t\t" + new Date());

    ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();

    service.schedule(() -> {
        throw new RuntimeException("The Executor task 1");
    }, 60, TimeUnit.SECONDS);
    service.schedule(() -> {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("The Executor task 2: \ t" + new Date());
    }, 60, TimeUnit.SECONDS);
}Copy the code

Output:

Start: Sun Oct 08 11:33:35 CST 2017 Executor Task 2: Sun Oct 08 11:34:36 CST 2017Copy the code

You can see that although task 1 throws a runtime exception here, task 2 executes successfully due to a well-developed exception handling mechanism within the thread pool.

Afterword.

After all of Timer’s flaws, are you still hesitating? Hurry up to give up Timer, into the arms of ScheduledThreadPoolExecutor!!!!