【多线程编程】--任务调度类ThreadPoolTaskScheduler、ScheduledThreadPoolExecutor解析

一、前言

springboot的常见的任务调度
这篇文章介绍了一些常见的任务调度技术方法。接下来,将会对ThreadPoolTaskScheduler、ScheduledThreadPoolExecutor进行对比。通过简单使用、源码实现、优缺点等来分析,以便在各种应用场景上使用相应技术。

说明:下面介绍的技术主要是针对 非分布式环境下可以考虑采用。

ScheduledThreadPoolExecutor适用场景:主要是延迟一次性任务、周期性任务;
ThreadPoolTaskScheduler适用场景:延迟任务、带有定时计划【可用cron表达式表示】的周期性任务;

【备注:在分布式环境下,如果要求延迟任务可参考实现延迟队列业务的场景该文章;如果要求周期性任务/定时任务任务调度@Scheduled、ScheduledThreadPoolExecutor、quartz、xxl-job可参考文章的quartz/xxl-job等分布式组件】

二、ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor类主要借助ThreadPoolExecutor的延迟阻塞队列DelayedWorkQueue
(1)、支持提交延迟任务schedule()、周期性任务scheduleAtFixedRate()或scheduleWithFixedDelay();

2.1、提交延迟任务schedule()

这个主要是延迟性任务调用方法。ScheduledThreadPoolExecutor.schedule会将用户任务包装为ScheduledFutureTask。ScheduledFutureTask是FutureTask的子类,所以有异常也是不抛出,被记录下来。
schedule()源码如下:

scheduler.schedule(new Runnable() {
   
    
    
            @Override
            public void run() {
   
    
    
                //只是延迟,然后执行一次
                System.out.println("addDelayTask start."+ TimeUtil.currentDatetime());
            }
        },10,TimeUnit.SECONDS);

ScheduledThreadPoolExecutor类的
public ScheduledFuture<?> schedule(Runnable command,
                                       long delay,
                                       TimeUnit unit) {
   
    
    
        if (command == null || unit == null)
            throw new NullPointerException();
        //将外部传入的任务封装成延迟任务对象ScheduledFutureTask
        RunnableScheduledFuture<?> t = decorateTask(command,
            new ScheduledFutureTask<Void>(command, null,
                                          triggerTime(delay, unit)));
        delayedExecute(t); //后面介绍
        return t;
    }

triggerTime()计算延迟时间,触发当前任务执行的时间----【当前时间 + 延迟时间】

 private long triggerTime(long delay, TimeUnit unit) {
   
    
    
        return triggerTime(unit.toNanos((delay < 0) ? 0 : delay));
    }

2.1.1、延迟执行任务delayedExecute方法

private void delayedExecute(RunnableScheduledFuture<?> task) {
   
    
    
        if (isShutdown())
            reject(task); //如果是被中断,那么按照拒绝策略实行
        else {
   
    
    
            super.getQueue().add(task); //向阻塞队列中添加任务
            //线程池状态为 SHUTDOWN 并且不允许执行任务了,就从队列删除该任务,并设置任务的状态为取消状态
            if (isShutdown() &&
                !canRunInCurrentRunState(task.isPeriodic()) &&
                remove(task))
                task.cancel(false);
            else
                ensurePrestart(); //进入ThreadPoolExecutor类逻辑
        }
    }

进入ThreadPoolExecutor类的,下面主要是 addWorker()方法,就是添加线程到线程池,返回 true 表示创建 Worker 成功,且启动线程

void ensurePrestart() {
   
    
    
        int wc = workerCountOf(ctl.get());
        if (wc < corePoolSize)
            addWorker(null, true);
        else if (wc == 0)
            addWorker(null, false);
    }

2.2、延迟任务队列delayedQueue

前面addWorker()方法将工作线程加入线程池,按照线程池原理,它是阻塞在获取数据的take()方法。
—因此,重点关注下DelayedWorkQueue队列下,如何获取任务
可参考queue队列解析 有说明延迟队列的原理。

2.2.1、DelayedWorkQueue#take()方法获取延迟任务

该方法主要获取延迟队列中任务延迟时间小于等于0 的任务。
如果延迟时间不小于0,那么调用条件队列的awaitNanos(delay)阻塞方法等待一段时间,等时间到了,延迟时间自然小于等于0了。
获取到任务后,工作线程就可以开始执行调度任务了。
-------从前面可以看出,我们将task线程封装成ScheduledFutureTask对象,那么,从延迟队列拿到数据,就开始执行run()方法。

2.2.2、ScheduledFutureTask#run()方法运行任务

 public void run() {
   
    
    
          //判断任务是否是周期性任务还是非周期性任务
            boolean periodic = isPeriodic(); 
            //是否是周期性任务,且检测当前状态能否执行,不能执行就取消
            if (!canRunInCurrentRunState(periodic))
                cancel(false);
            else if (!periodic) //非周期任务,直接调用 FutureTask#run 执行一次
                ScheduledFutureTask.super.run();
            else if (ScheduledFutureTask.super.runAndReset()) {
   
    
    //周期任务的执行
                setNextRunTime(); //设置周期任务的下一个执行时间
                // 任务的下一次执行安排,如果当前线程池状态可以执行周期任务,加入队列,并开启新线程
                reExecutePeriodic(outerTask);
            }
   }

ScheduledFutureTask.super.runAndReset()执行周期性任务,周期任务正常完成后任务的状态不会变化,依旧是 NEW;
如果本次任务执行出现异常,会进入 setException 方法将任务状态置为异常,后续的该任务将不会再周期的执行;

2.3、周期性任务scheduleAtFixedRate()

猜你喜欢

转载自blog.csdn.net/xunmengyou1990/article/details/128696050
今日推荐