• Timer和ScheduledThreadPoolExecutor的区别及源码分析


    Timer

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

    复制代码
    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),如果执行时间小于等于当前系统时间则执行任务,否则继续等待(执行时间-当前时间)。

    ScheduledThreadPoolExecutor

    基于多线程、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时间差值计算得到)进行判断任务的执行)。基于系统时间执行的任务则无法精确(因为系统时间可以随时调整)!

     

  • 相关阅读:
    “一种三元前驱体废水螯合树脂回收钴的装置”实用新型专利
    Vue项目中,Async与Await设置多个Axios异步请求的执行顺序
    C++使用Windows API- GetModuleFileName获取可执行文件路径方法。
    SpringBoot(三) - Slf4j+logback 日志,异步请求,定时任务
    java基于微信小程序的家电维修预约系统 uniapp 小程序
    新媒体运营的营销方案
    他山之石 | 日本品牌出海,商社扮演了重要角色
    【kotlin】Kotlin学习:run、with、apply、also、let的区别
    存储bag文件并转csv,一键启动思路、默认python3
    基于单片机的机械臂运行轨迹在线控制系统设计
  • 原文地址:https://www.cnblogs.com/hdwang/p/16436142.html