Timer和ScheduledThreadPoolExecutor的区别
阅读原文时间:2022年07月02日阅读:1

基于单线程、系统时间实现的延时、定期任务执行类。具体可以看下面红色标注的代码。

public class Timer {
/**
* The timer task queue. This data structure is shared with the timer
* thread. The timer produces tasks, via its various schedule calls,
* and the timer thread consumes, executing timer tasks as appropriate,
* and removing them from the queue when they're obsolete.
*/
private final TaskQueue queue = new TaskQueue();

/\*\*  
 \* The timer thread.\*/  
private final TimerThread thread = new TimerThread(queue);

class TimerThread extends Thread {
/**
* This flag is set to false by the reaper to inform us that there
* are no more live references to our Timer object. Once this flag
* is true and there are no more tasks in our queue, there is no
* work left for us to do, so we terminate gracefully. Note that
* this field is protected by queue's monitor!
*/
boolean newTasksMayBeScheduled = true;

/\*\*  
 \* Our Timer's queue.  We store this reference in preference to  
 \* a reference to the Timer so the reference graph remains acyclic.  
 \* Otherwise, the Timer would never be garbage-collected and this  
 \* thread would never go away.  
 \*/  
private TaskQueue queue;

TimerThread(TaskQueue queue) {  
    this.queue = queue;  
}

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

/\*\*  
 \* The main timer loop.  (See class comment.)  
 \*/  
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) {  
        }  
    }  
}  

}

Timer延时、定时任务的实现采用单线程,在主循环(mainLoop)中循环遍历任务队列(TaskQueue),如果执行时间小于等于当前系统时间则执行任务,否则继续等待(执行时间-当前时间)。

基于多线程、JVM时间实现的延时、定期任务执行类。具体可以看下面红色标注的代码。

public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}

DelayedWorkQueue中的take方法

public RunnableScheduledFuture take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
for (;;) {
RunnableScheduledFuture first = queue[0];
if (first == null)
available.await();
else {
long delay = first.getDelay(NANOSECONDS);
if (delay <= 0)
return finishPoll(first);
first = null; // don't retain ref while waiting
if (leader != null)
available.await();
else {
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
available.awaitNanos(delay);
} finally {
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
if (leader == null && queue[0] != null)
available.signal();
lock.unlock();
}
}

public long getDelay(TimeUnit unit) {
return unit.convert(time - now(), NANOSECONDS);
}

/\*\*  
 \* Returns current nanosecond time.  
 \*/  
final long now() {  
    return System.nanoTime();  
}

ThreadPoolExecutor执行流程

submit(task)->execute(task)
->1.当前线程数<核心线程数: addWorker(核心工作者线程)->runWorker-> 循环【getTask(workQueue.take)->task.run】
->2.当前线程数>=核心线程数:排队任务成功:task add to workQueue(BlockingQueue)->addWorker(非核心工作者线程)……
->3.当前线程数>=核心线程数:排队任务失败:尝试添加新线程执行任务 addWorker(非核心工作者线程)……

ScheduledThreadPoolExecutor执行延时、定期任务,核心代码就在runWorker,循环获取任务队列中的任务然后执行,在获取任务的时候如果任务的执行时间没到,则进行等待。延时时间的计算都是基于System.nanoTime(),即JVM时间。

优缺点:

1.Timer单线程,执行周期任务时,一次出错,则TimerThread线程终止, 所有任务将无法执行。而且任务的执行时间可能会影响周期的准确性。

2.Timer基于系统时间,系统时间的修改会影响任务的执行。在以系统时间为准的场景中(public void schedule(TimerTask task, Date time))使用非常合适,使用周期性任务则受到极大影响,因为时间间隔被破坏!

3.ScheduledThreadPoolExecutor多线程,任务的执行不会相互影响,且能保证执行时间间隔的准确性。

4.ScheduledThreadPoolExecutor基于JVM时间,该时间本身无任何意义,仅用来计算时间间隔,不受系统时间影响。所以用来计算周期间隔特别合适,而且单位是纳秒更加精确。因此延时任务、周期任务采用它比Timer更加靠谱!

总结:

Timer的使用场景,仅在基于系统时间为准的场景中非常合适(依赖当前系统时间进行判断任务的执行)。

ScheduledThreadPoolExecutor的使用场景则更为广泛,对延时任务、周期任务使用此类更靠谱(依赖时间间隔(JVM时间差值计算得到)进行判断任务的执行)。基于系统时间执行的任务则无法精确(因为系统时间可以随时调整)!