并发ScheduledThreadPoolExecutor源码分析

ScheduledThreadPoolExecutor是一个可以在指定一定延时时间后或者定时进行任务调度的线程池,ScheduledThreadPoolExecutor继承了ThreadPoolExecutor并实现了ScheduledExecutorService接口。线程池的队列是DelayedWorkQueue(他是ScheduledThreadPoolExecutor的一个内部类)。
在这里插入图片描述

还要在看一下ScheduledFutureTask(同样是ScheduledThreadPoolExecutor的一个内部类),继承FutureTask,FutureTask的内部有一个变量state用来表示任务的状态,一开始状态为NEW。下面是所有状态定义。

 private volatile int state;
 private static final int NEW          = 0;//初始化状态
 private static final int COMPLETING   = 1;//执行中状态
 private static final int NORMAL       = 2;//正常运行结束状态
 private static final int EXCEPTIONAL  = 3;//运行中异常
 private static final int CANCELLED    = 4;//任务被取消
 private static final int INTERRUPTING = 5;//任务正在被中段
 private static final int INTERRUPTED  = 6;//任务已经被中断

ScheduledFutureTask内部还有一个变量period用来表示任务的类型,如果==0,则表示任务是一次性的,任务执行完毕后退出,如果为负数,说明当前任务是以固定延时的定时可重复执行任务,如果为正数,说明任务是以固定频率的定时定时可重复执行任务。

一、chedule(Runnable command, long delay, TimeUnit unit)方法解析

他的作用是提交一个延时执行的任务,任务从提交时间算起延时单位为unit的delay时间后开始执行,任务只会执行一次。

 public ScheduledFuture<?> schedule(Runnable command,
                                    long delay,
                                    TimeUnit unit) {
      //判断参数是否为空,空则跑出异常
     if (command == null || unit == null)
         throw new NullPointerException();
     //任务转换
     RunnableScheduledFuture<?> t = decorateTask(command,
         new ScheduledFutureTask<Void>(command, null,
                                       triggerTime(delay, unit)));
      //添加任务到延时队列
     delayedExecute(t);
     return t;
 }

首先是参数判断,为空则抛出异常,接着是装饰任务,把提交的command(Runnable对象)转换为ScheduledFutureTask,ScheduledFutureTask是具体放入延时队列里面的东西,由于是延时任务,所以ScheduledFutureTask实现了getDelay和compareTo方法,triggerTime方法将延时时间转换为绝对时间,也就是把当前时间的纳秒加上延迟的纳秒后的值。

ScheduledFutureTask的构造如下,设period为0,表示该任务是一次性任务。

 ScheduledFutureTask(Callable<V> callable, long ns) {
     super(callable);
     this.time = ns;
     this.period = 0;
     this.sequenceNumber = sequencer.getAndIncrement();
 }

然后通过delayedExecute将任务添加到延时队列。

  private void delayedExecute(RunnableScheduledFuture<?> task) {
  		//如果线程池管理,则执行线程拒绝策略
      if (isShutdown())
          reject(task);
      else {
      		//添加任务到延时队列
          super.getQueue().add(task);
          //再次检查线程池状态
          if (isShutdown() &&
              !canRunInCurrentRunState(task.isPeriodic()) &&
              remove(task))
              task.cancel(false);
          else
          //确保至少一个线程在处理任务
              ensurePrestart();
      }
  }

上述代码首先确保线程池没关闭,关闭则执行拒绝策略,没关闭将任务添加到延时队列,添加后再重新检查线程池是否关闭,如果关闭则从延时队列里面删除刚才添加的任务。

再看ensurePrestart方法。

 void ensurePrestart() {
     int wc = workerCountOf(ctl.get());
	//增加核心线程数
     if (wc < corePoolSize)
         addWorker(null, true);
     else if (wc == 0)
         addWorker(null, false);
 }

首先获取到了线程池中的线程数,如果个数小于核心线程池则新增一个线程,否则如果当前线程数为0个,则同样新增一个线程。

我们知道ThreadPoolExecutor在具体执行任务的线程是Worker线程,Worker线程调用具体任务的run方法来执行,在这里的任务是ScheduledFutureTask,所以来看看ScheduledFutureTask的run方法。

public void run() {
	//是否执行一次
    boolean periodic = isPeriodic();
    //取消任务
    if (!canRunInCurrentRunState(periodic))
        cancel(false);
    else if (!periodic)
        ScheduledFutureTask.super.run();
        //定时任务
    else if (ScheduledFutureTask.super.runAndReset()) {
        setNextRunTime();
        reExecutePeriodic(outerTask);
    }
}

首先判断任务是一次性的还是可重复执行的任务,ScheduledFutureTask在构造方法中已经设置了period为0,所以,这里会返回false。

 public boolean isPeriodic() {
     return period != 0;
 }

然后判断当前任务是否应该被取消。为true则取消任务。

由于periodic为false,则会执行代码ScheduledFutureTask.super.run();,调用父类FutureTask的run方法。

 public void run() {
     if (state != NEW ||
         !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                      null, Thread.currentThread()))
         return;
     try {
         Callable<V> c = callable;
         if (c != null && state == NEW) {
             V result;
             boolean ran;
             try {
                 result = c.call();
                 ran = true;
             } catch (Throwable ex) {
                 result = null;
                 ran = false;
                 setException(ex);
             }
             if (ran)
                 set(result);
         }
     } finally {
         runner = null;

         int s = state;
         if (s >= INTERRUPTING)
             handlePossibleCancellationInterrupt(s);
     }
 }

FutureTask.run()首先判断任务状态,如果不是NEW则直接返回,或者如果任务状态为NEW,但是使用CAS设置当前任务的持有者为当前线程失败则直接返回。

然后具体调用callable的call方法执行任务,如果任务执行成功则修改任务状态,也就是set方法。

protected void set(V v) {
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        outcome = v;
        UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
        finishCompletion();
    }
}

使用CAS将当前任务的状态从NEW转换的COMPLETING。这里当有多个线程调用时只有一个线程会成功,成功的线程在通过 UNSAFE.putOrderedInt设置任务的状态为正常结束状态。

还有在任务执行失败后,执行setException方法,和set方法类似了。

 protected void setException(Throwable t) {
     if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
         outcome = t;
         UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
         finishCompletion();
     }
 }

二、scheduleWithFixedDelay(Runnable command,long initialDelay, long delay,TimeUnit unit)方法解析

他的作用是,当任务执行完毕后,让其延迟固定时间后再次运行,initialDelay表示提交任务后延迟多少时间开始执行任务command,delay表示当任务执行完毕后延长多少时间后再次运行command,unit是时间单位。

这个任务会一直重复运行下去,直到任务中抛出异常、被取消、线程池关闭。

 public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                  long initialDelay,
                                                  long delay,
                                                  TimeUnit unit) {
      //参数判断
     if (command == null || unit == null)
         throw new NullPointerException();
     if (delay <= 0)
         throw new IllegalArgumentException();
      //任务转换
     ScheduledFutureTask<Void> sft =
         new ScheduledFutureTask<Void>(command,
                                       null,
                                       triggerTime(initialDelay, unit),
                                       unit.toNanos(-delay));
     RunnableScheduledFuture<Void> t = decorateTask(command, sft);
     sft.outerTask = t;
     //添加任务到队列
     delayedExecute(t);
     return t;
 }

首先也是参数判断,为空则抛出异常,然后将command任务转换为ScheduledFutureTask,然后添加延迟到队列。

将任务添加到队列后线程池线程会从队列中获取任务,然后调用ScheduledFutureTask的run方法,由于这里period<0,所以isPeriodic返回true,则会执行方法runAndReset()。

protected boolean runAndReset() {
    if (state != NEW ||
        !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                     null, Thread.currentThread()))
        return false;
    boolean ran = false;
    int s = state;
    try {
        Callable<V> c = callable;
        if (c != null && s == NEW) {
            try {
                c.call(); 
                ran = true;
            } catch (Throwable ex) {
                setException(ex);
            }
        }
    } finally {
        runner = null;
        s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
    return ran && s == NEW;
}

他在任务执行完毕后不会设置任务的状态,是为了让任务可重复执行,看最后一句,判断如果当前任务正常执行完毕并且任务状态为NEW则返回true,如果返回true则执行方法setNextRunTime(),用于设置任务下一次的执行时间。

这里p是<0的,然后设置timer为当前时间加上-p,也就是延迟-p时间后再次执行。

 private void setNextRunTime() {
     long p = period;
     if (p > 0)
         time += p;
     else
         time = triggerTime(-p);
 }

三、scheduleAtFixedRate(Runnable command, long initialDelay, long period,TimeUnit unit)方法解析

该方法相对起始时间点以固定频率调用指定任务,当把任务提交到线程池并延迟initialDelay时间后开始执行任务command,然后从initialDelay+period时间点再次执行,而后在initialDelay+2*period时间点再次执行,直到抛出异常或者取消、关闭线程池。

原理和scheduleWithFixedDelay类似,我们就看几个不同点,

 public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                               long initialDelay,
                                               long period,
                                               TimeUnit unit) {
     if (command == null || unit == null)
         throw new NullPointerException();
     if (period <= 0)
         throw new IllegalArgumentException();
     ScheduledFutureTask<Void> sft =
         new ScheduledFutureTask<Void>(command,
                                       null,
                                       triggerTime(initialDelay, unit),
                                       unit.toNanos(period));
     RunnableScheduledFuture<Void> t = decorateTask(command, sft);
     sft.outerTask = t;
     delayedExecute(t);
     return t;
 }

首先是period=period,不再是-period。所以当前任务执行完毕后调用setNextRunTime设置任务下次执行的时间是 time += p。

 private void setNextRunTime() {
     long p = period;
     if (p > 0)
         time += p;
     else
         time = triggerTime(-p);
 }

如果当前任务还没有执行完,下一次执行任务的时间到了,则不会并发执行,下次要执行的任务会延迟,要等到当前任务执行完毕后再次执行

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值