Timer和ScheduledThreadPoolExecutor的区别及源码分析

Timer

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

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

ScheduledThreadPoolExecutor

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

ScheduledThreadPoolExecutor执行流程总结:

1.schedule(定期执行方法)

2.new Task(ScheduledFutureTask)(构建任务作业)

3.delayedExecute(task) (延时执行任务)

4.workQueue.add(task) (任务作业添加到阻塞队列)

5.addWorker(null, true or false) (添加核心作业线程,当核心线程数设置为0时则启动一个非核心线程)

6.runWorker(运行作业线程)

7.循环:getTask ->workQueue.take(获取作业)

8.task.run (作业运行)->ScheduledFutureTask.run(reExecutePeriodic(outerTask)-> workQueue.add(task)……) (周期作业重复调用)

(核心线程 或 非核心线程循环从队列中获取Task执行,周期任务则将任务重新排队)

DelayedWorkQueue中的take方法

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

ThreadPoolExecutor执行流程(参考)

submit(task)-> execute(task)
->1 当前线程数

2.1 任务排队成功:addWorker(null, false)(非核心工作者线程)-> 循环【getTask(workQueue.take)->task.run】(非核心线程循环从队列中获取Task执行)

2.2 任务排队失败:addWorker(task, false) (非核心工作者线程)-> task.run (尝试添加非核心线程执行任务)

优缺点:

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

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

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

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

总结:

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

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

Original: https://www.cnblogs.com/hdwang/p/16436142.html
Author: 追极
Title: Timer和ScheduledThreadPoolExecutor的区别及源码分析

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/587759/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球