• 并发编程(十五)——定时器 ScheduledThreadPoolExecutor 实现原理与源码深度解析


    在上一篇线程池的文章《并发编程(十一)—— Java 线程池 实现原理与源码深度解析(一)》中从ThreadPoolExecutor源码分析了其运行机制。限于篇幅,留下了ScheduledThreadPoolExecutor未做分析,因此本文继续从源代码出发分析ScheduledThreadPoolExecutor的内部原理。

    类声明

    1 public class ScheduledThreadPoolExecutor
    2         extends ThreadPoolExecutor
    3         implements ScheduledExecutorService {

    ScheduledThreadPoolExecutor继承了ThreadPoolExecutor,实现了ScheduledExecutorService。因此它具有ThreadPoolExecutor的所有能力。所不同的是它具有定时执行,以周期或间隔循环执行任务等功能。

    这里我们先看下ScheduledExecutorService的源码:

    ScheduledExecutorService

     1 //可调度的执行者服务接口
     2 public interface ScheduledExecutorService extends ExecutorService {
     3 
     4     //指定时延后调度执行任务,只执行一次,没有返回值
     5     public ScheduledFuture<?> schedule(Runnable command,
     6                                        long delay, TimeUnit unit);
     7 
     8     //指定时延后调度执行任务,只执行一次,有返回值
     9     public <V> ScheduledFuture<V> schedule(Callable<V> callable,
    10                                            long delay, TimeUnit unit);
    11 
    12     //指定时延后开始执行任务,以后每隔period的时长再次执行该任务
    13     public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
    14                                                   long initialDelay,
    15                                                   long period,
    16                                                   TimeUnit unit);
    17 
    18     //指定时延后开始执行任务,以后任务执行完成后等待delay时长,再次执行任务
    19     public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
    20                                                      long initialDelay,
    21                                                      long delay,
    22                                                      TimeUnit unit);
    23 }

    其中schedule方法用于单次调度执行任务。这里主要理解下后面两个方法。

    • scheduleAtFixedRate:该方法在initialDelay时长后第一次执行任务,以后每隔period时长,再次执行任务。注意,period是从任务开始执行算起的。开始执行任务后,定时器每隔period时长检查该任务是否完成,如果完成则再次启动任务,否则等该任务结束后才再次启动任务,看下图示例

    • scheduleWithFixDelay:该方法在initialDelay时长后第一次执行任务,以后每当任务执行完成后,等待delay时长,再次执行任务,看下图示例。

    使用例子

    1、schedule(Runnable command,long delay, TimeUnit unit)

     1 /**
     2  * @author: ChenHao
     3  * @Date: Created in 14:54 2019/1/11
     4  */
     5 public class Test1 {
     6     public static void main(String[] args) throws ExecutionException, InterruptedException {
     7         // 延迟1s后开始执行,只执行一次,没有返回值
     8         ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(10);
     9         ScheduledFuture<?> result = executorService.schedule(new Runnable() {
    10             @Override
    11             public void run() {
    12                 System.out.println("gh");
    13                 try {
    14                     Thread.sleep(3000);
    15                 } catch (InterruptedException e) {
    16                     // TODO Auto-generated catch block
    17                     e.printStackTrace();
    18                 }
    19             }
    20         }, 1000, TimeUnit.MILLISECONDS);
    21         System.out.println(result.get());
    22     }
    23 }

    运行结果:

    2、schedule(Callable<V> callable, long delay, TimeUnit unit);

     1 public class Test2 {
     2     public static void main(String[] args) throws ExecutionException, InterruptedException {
     3         // 延迟1s后开始执行,只执行一次,有返回值
     4         ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(10);
     5         ScheduledFuture<String> result = executorService.schedule(new Callable<String>() {
     6             @Override
     7             public String call() throws Exception {
     8                 try {
     9                     Thread.sleep(3000);
    10                 } catch (InterruptedException e) {
    11                     // TODO Auto-generated catch block
    12                     e.printStackTrace();
    13                 }
    14                 return "ghq";
    15             }
    16         }, 1000, TimeUnit.MILLISECONDS);
    17         // 阻塞,直到任务执行完成
    18         System.out.print(result.get());
    19     }
    20 }

    运行结果:

    3、scheduleAtFixedRate

    /**
     * @author: ChenHao
     * @Date: Created in 14:54 2019/1/11
     */
    public class Test3 {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(10);
            // 从加入任务开始算1s后开始执行任务,1+2s开始执行,1+2*2s执行,1+n*2s开始执行;
            // 但是如果执行任务时间大于2s则不会并发执行后续任务,当前执行完后,接着执行下次任务。
            ScheduledFuture<?> result = executorService.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    System.out.println(System.currentTimeMillis());
                }
            }, 1000, 2000, TimeUnit.MILLISECONDS);
            
            //一个ScheduledExecutorService里可以同时添加多个定时任务,这样就是形成堆
            ScheduledFuture<?> result2 = executorService.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    System.out.println(System.currentTimeMillis());
                }
            }, 1000, 2000, TimeUnit.MILLISECONDS);
        }
    }

    这里可以看到一个ScheduledExecutorService 中可以添加多个定时任务,这是就会形成堆

    运行结果:

    4、scheduleWithFixedDelay

     1 /**
     2  * @author: ChenHao
     3  * @Date: Created in 14:54 2019/1/11
     4  */
     5 public class Test4 {
     6     public static void main(String[] args) throws ExecutionException, InterruptedException {
     7         //任务间以固定时间间隔执行,延迟1s后开始执行任务,任务执行完毕后间隔2s再次执行,任务执行完毕后间隔2s再次执行,依次往复
     8         ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(10);
     9         ScheduledFuture<?> result = executorService.scheduleWithFixedDelay(new Runnable() {
    10             @Override
    11             public void run() {
    12                 System.out.println(System.currentTimeMillis());
    13             }
    14         }, 1000, 2000, TimeUnit.MILLISECONDS);
    15 
    16         // 由于是定时任务,一直不会返回
    17         result.get();
    18         System.out.println("over");
    19     }
    20 }

    运行结果:

    源码分析

    构造器

    1 public ScheduledThreadPoolExecutor(int corePoolSize) {
    2        super(corePoolSize, Integer.MAX_VALUE, 0, TimeUnit.NANOSECONDS,
    3              new DelayedWorkQueue());
    4 }

     内部其实都是调用了父类ThreadPoolExecutor的构造器,因此它具有ThreadPoolExecutor的所有能力。

     通过super方法的参数可知,核心线程的数量即传入的参数,而线程池的线程数为Integer.MAX_VALUE,几乎为无上限。
     这里采用了DelayedWorkQueue任务队列,也是定时任务的核心,是一种优先队列,时间小的排在前面,所以获取任务的时候就能先获取到时间最小的执行,可以看我上篇文章《并发编程(十四)—— ScheduledThreadPoolExecutor 实现原理与源码深度解析 之 DelayedWorkQueue》。

     由于这里队列没有定义大小,所以队列不会添加满,因此最大的线程数就是核心线程数,超过核心线程数的任务就放在队列里,并不重新开启临时线程。

    我们先来看看几个入口方法的实现:

     1 public ScheduledFuture<?> schedule(Runnable command,
     2                                    long delay,
     3                                    TimeUnit unit) {
     4     if (command == null || unit == null)
     5         throw new NullPointerException();
     6     RunnableScheduledFuture<?> t = decorateTask(command,
     7         new ScheduledFutureTask<Void>(command, null,
     8                                       triggerTime(delay, unit)));
     9     delayedExecute(t);
    10     return t;
    11 }
    12 
    13 public <V> ScheduledFuture<V> schedule(Callable<V> callable,
    14                                        long delay,
    15                                        TimeUnit unit) {
    16     if (callable == null || unit == null)
    17         throw new NullPointerException();
    18     RunnableScheduledFuture<V> t = decorateTask(callable,
    19         new ScheduledFutureTask<V>(callable,
    20                                    triggerTime(delay, unit)));
    21     delayedExecute(t);
    22     return t;
    23 }
    24 
    25 public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
    26                                               long initialDelay,
    27                                               long period,
    28                                               TimeUnit unit) {
    29     if (command == null || unit == null)
    30         throw new NullPointerException();
    31     if (period <= 0)
    32         throw new IllegalArgumentException();
    33     ScheduledFutureTask<Void> sft =
    34         new ScheduledFutureTask<Void>(command,
    35                                       null,
    36                                       triggerTime(initialDelay, unit),
    37                                       unit.toNanos(period));
    38     RunnableScheduledFuture<Void> t = decorateTask(command, sft);
    39     sft.outerTask = t;
    40     delayedExecute(t);
    41     return t;
    42 }
    43 
    44 public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
    45                                                  long initialDelay,
    46                                                  long delay,
    47                                                  TimeUnit unit) {
    48     if (command == null || unit == null)
    49         throw new NullPointerException();
    50     if (delay <= 0)
    51         throw new IllegalArgumentException();
    52     ScheduledFutureTask<Void> sft =
    53         new ScheduledFutureTask<Void>(command,
    54                                       null,
    55                                       triggerTime(initialDelay, unit),
    56                                       unit.toNanos(-delay));
    57     RunnableScheduledFuture<Void> t = decorateTask(command, sft);
    58     sft.outerTask = t;
    59     delayedExecute(t);
    60     return t;
    61 }

     这几个方法都是将任务封装成了ScheduledFutureTask,上面做的首先把runnable装饰为delay队列所需要的格式的元素,然后把元素加入到阻塞队列,然后线程池线程会从阻塞队列获取超时的元素任务进行处理,下面看下队列元素如何实现的。

    ScheduledFutureTask

    ScheduledFutureTask是一个延时定时任务,它可以返回任务剩余延时时间,可以被周期性地执行。

    属性

     1 private class ScheduledFutureTask<V>
     2         extends FutureTask<V> implements RunnableScheduledFuture<V> {
     3         /** 是一个序列,每次创建任务的时候,都会自增。 */
     4         private final long sequenceNumber;
     5 
     6         /** 任务能够开始执行的时间 */
     7         private long time;
     8 
     9         /**
    10          * 任务周期执行的时间
    11          * 0表示不是一个周期定时任务
    12          * 正数表示固定周期时间去执行任务
    13          * 负数表示任务完成之后,延时period时间再去执行任务
    14          */
    15         private final long period;
    16 
    17         /** 表示再次执行的任务,在reExecutePeriodic中调用 */
    18         RunnableScheduledFuture<V> outerTask = this;
    19 
    20         /**
    21          * 表示在任务队列中的索引位置,用来支持快速从队列中删除任务。
    22          */
    23         int heapIndex;
    24 }

    ScheduledFutureTask继承了 FutureTask 和 RunnableScheduledFuture

    属性说明:

    1. sequenceNumber: 是一个序列,每次创建任务的时候,都会自增。
    2. time: 任务能够开始执行的时间。
    3. period: 任务周期执行的时间。0表示不是一个周期定时任务。
    4. outerTask: 表示再次执行的任务,在reExecutePeriodic中调用
    5. heapIndex: 表示在任务队列中的索引位置,用来支持快速从队列中删除任务。

    构造器

    • 创建延时任务

     1 /**
     2  * 创建延时任务
     3  */
     4 ScheduledFutureTask(Runnable r, V result, long ns) {
     5     // 调用父类的方法
     6     super(r, result);
     7     // 任务开始的时间
     8     this.time = ns;
     9     // period是0,不是一个周期定时任务
    10     this.period = 0;
    11     // 每次创建任务的时候,sequenceNumber都会自增
    12     this.sequenceNumber = sequencer.getAndIncrement();
    13 }
    14 
    15  /**
    16  * 创建延时任务
    17  */
    18 ScheduledFutureTask(Callable<V> callable, long ns) {
    19     // 调用父类的方法
    20     super(callable);
    21     // 任务开始的时间
    22     this.time = ns;
    23     // period是0,不是一个周期定时任务
    24     this.period = 0;
    25     // 每次创建任务的时候,sequenceNumber都会自增
    26     this.sequenceNumber = sequencer.getAndIncrement();
    27 }

     我们看看super(),其实就是FutureTask 里面的构造方法,关于FutureTask 可以看看我之前的文章《Java 多线程(五)—— 线程池基础 之 FutureTask源码解析

     1 public FutureTask(Runnable runnable, V result) {
     2     this.callable = Executors.callable(runnable, result);
     3     this.state = NEW;       // ensure visibility of callable
     4 }
     5 public FutureTask(Callable<V> callable) {
     6     if (callable == null)
     7         throw new NullPointerException();
     8     this.callable = callable;
     9     this.state = NEW;       // ensure visibility of callable
    10 }
    • 创建延时定时任务
     1 /**
     2  * 创建延时定时任务
     3  */
     4 ScheduledFutureTask(Runnable r, V result, long ns, long period) {
     5     // 调用父类的方法
     6     super(r, result);
     7     // 任务开始的时间
     8     this.time = ns;
     9     // 周期定时时间
    10     this.period = period;
    11     // 每次创建任务的时候,sequenceNumber都会自增
    12     this.sequenceNumber = sequencer.getAndIncrement();
    13 }

    延时定时任务不同的是设置了period,后面通过判断period是否为0来确定是否是定时任务。

    run()

     1 public void run() {
     2     // 是否是周期任务
     3     boolean periodic = isPeriodic();
     4     // 如果不能在当前状态下运行,那么就要取消任务
     5     if (!canRunInCurrentRunState(periodic))
     6         cancel(false);
     7     // 如果只是延时任务,那么就调用run方法,运行任务。
     8     else if (!periodic)
     9         ScheduledFutureTask.super.run();
    10     // 如果是周期定时任务,调用runAndReset方法,运行任务。
    11     // 这个方法不会改变任务的状态,所以可以反复执行。
    12     else if (ScheduledFutureTask.super.runAndReset()) {
    13         // 设置周期任务下一次执行的开始时间time
    14         setNextRunTime();
    15         // 重新执行任务outerTask
    16         reExecutePeriodic(outerTask);
    17     }
    18 }

    这个方法会在ThreadPoolExecutor的runWorker方法中调用,而且这个方法调用,说明肯定已经到了任务的开始时间time了。这个方法我们待会会再继续来回看一下

    1. 先判断当前线程状态能不能运行任务,如果不能,就调用cancel()方法取消本任务。
    2. 如果任务只是一个延时任务,那么调用父类的run()运行任务,改变任务的状态,表示任务已经运行完成了。
    3. 如果任务只是一个周期定时任务,那么就任务必须能够反复执行,那么就不能调用run()方法,它会改变任务的状态。而是调用runAndReset()方法,只是简单地运行任务,而不会改变任务状态。
    4. 设置周期任务下一次执行的开始时间time,并重新执行任务。

    schedule(Runnable command, long delay,TimeUnit unit)

     1 public ScheduledFuture<?> schedule(Runnable command,
     2                                   long delay,
     3                                   TimeUnit unit) {
     4    if (command == null || unit == null)
     5        throw new NullPointerException();
     6 
     7    //装饰任务,主要实现public long getDelay(TimeUnit unit)和int compareTo(Delayed other)方法
     8    RunnableScheduledFuture<?> t = decorateTask(command,
     9        new ScheduledFutureTask<Void>(command, null,
    10                                      triggerTime(delay, unit)));
    11    //添加任务到延迟队列
    12    delayedExecute(t);
    13    return t;
    14 }

     获取延时执行时间

     1 private long triggerTime(long delay, TimeUnit unit) {
     2     return triggerTime(unit.toNanos((delay < 0) ? 0 : delay));
     3 }
     4 
     5 /**
     6  * Returns the trigger time of a delayed action.
     7  */
     8 long triggerTime(long delay) {
     9     //当前时间加上延时时间
    10     return now() +
    11         ((delay < (Long.MAX_VALUE >> 1)) ? delay : overflowFree(delay));
    12 }

    上述的decorateTask方法把Runnable任务包装成ScheduledFutureTask,用户可以根据自己的需要覆写该方法:

    1 protected <V> RunnableScheduledFuture<V> decorateTask(Runnable runnable, RunnableScheduledFuture<V> task) {
    2     return task;
    3 }

    schedule的核心是其中的delayedExecute方法:

     1 private void delayedExecute(RunnableScheduledFuture<?> task) {
     2     if (isShutdown())   // 线程池已关闭
     3         reject(task);   // 任务拒绝策略
     4     else {
     5         //将任务添加到任务队列,会根据任务延时时间进行排序
     6         super.getQueue().add(task);
     7         // 如果线程池状态改变了,当前状态不能运行任务,那么就尝试移除任务,
     8         // 移除成功,就取消任务。
     9         if (isShutdown() && !canRunInCurrentRunState(task.isPeriodic()) && remove(task))
    10             task.cancel(false);  // 取消任务
    11         else
    12             // 预先启动工作线程,确保线程池中有工作线程。
    13             ensurePrestart();
    14     }
    15 }

    这个方法的主要作用就是将任务添加到任务队列中,因为这里任务队列是优先级队列DelayedWorkQueue,它会根据任务的延时时间进行排序。

    • 如果线程池不是RUNNING状态,不能执行延时任务task,那么调用reject(task)方法,拒绝执行任务task。

    • 将任务添加到任务队列中,会根据任务的延时时间进行排序。

    • 因为是多线程并发环境,就必须判断在添加任务的过程中,线程池状态是否被别的线程更改了,那么就可能要取消任务了。

    • 将任务添加到任务队列后,还要确保线程池中有工作线程,不然任务也不为执行。所以ensurePrestart()方法预先启动工作线程,确保线程池中有工作线程。

     1 void ensurePrestart() {
     2     // 线程池中的线程数量
     3     int wc = workerCountOf(ctl.get());
     4     // 如果小于核心池数量,就创建新的工作线程
     5     if (wc < corePoolSize)
     6         addWorker(null, true);
     7     // 说明corePoolSize数量是0,必须创建一个工作线程来执行任务
     8     else if (wc == 0)
     9         addWorker(null, false);
    10 }

    通过ensurePrestart可以看到,如果核心线程池未满,则新建的工作线程会被放到核心线程池中。如果核心线程池已经满了,ScheduledThreadPoolExecutor不会像ThreadPoolExecutor那样再去创建归属于非核心线程池的工作线程,加入到队列就完了,等待核心线程执行完任务再拉取队列里的任务。也就是说,在ScheduledThreadPoolExecutor中,一旦核心线程池满了,就不会再去创建工作线程。

    这里思考一点,什么时候会执行else if (wc == 0)创建一个归属于非核心线程池的工作线程?
    答案是,当通过setCorePoolSize方法设置核心线程池大小为0时,这里必须要保证任务能够被执行,所以会创建一个工作线程,放到非核心线程池中。

    看到 addWorker(null, true); 并没有将任务设置进入,而是设置的null, 则说明线程池里线程第一次启动时, runWorker中取到的 firstTask为null,需要通过 getTask() 从队列中取任务,这里可以看看我之前写的关于线程池的文章《并发编程(十一)—— Java 线程池 实现原理与源码深度解析(一)》。

    getTask()中  Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :workQueue.take();如果是存在核心线程则调用take(),如果传入的核心线程为0,则存在一个临时线程,调用poll(),这两个方法都会先获取时间,看看有没有达到执行时间,没有达到执行时间则阻塞,可以看看我上一篇文章,达到执行时间,则取到任务,就会执行下面的run方法。

     1 public void run() {
     2     // 是否是周期任务
     3     boolean periodic = isPeriodic();
     4     // 如果不能在当前状态下运行,那么就要取消任务
     5     if (!canRunInCurrentRunState(periodic))
     6         cancel(false);
     7     // 如果只是延时任务,那么就调用run方法,运行任务。
     8     else if (!periodic)
     9         ScheduledFutureTask.super.run();
    10     // 如果是周期定时任务,调用runAndReset方法,运行任务。
    11     // 这个方法不会改变任务的状态,所以可以反复执行。
    12     else if (ScheduledFutureTask.super.runAndReset()) {
    13         // 设置周期任务下一次执行的开始时间time
    14         setNextRunTime();
    15         // 重新执行任务outerTask
    16         reExecutePeriodic(outerTask);
    17     }
    18 }
    19 
    20 public boolean isPeriodic() {
    21     return period != 0;
    22 }

     schedule不是周期任务,那么调用父类的run()运行任务,改变任务的状态,表示任务已经运行完成了。

    scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit)

     1 public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
     2                                              long initialDelay,
     3                                              long period,
     4                                              TimeUnit unit) {
     5    if (command == null || unit == null)
     6        throw new NullPointerException();
     7    if (period <= 0)
     8        throw new IllegalArgumentException();
     9    //装饰任务类,注意period=period>0,不是负的
    10    ScheduledFutureTask<Void> sft =
    11        new ScheduledFutureTask<Void>(command,
    12                                      null,
    13                                      triggerTime(initialDelay, unit),
    14                                      unit.toNanos(period));
    15    RunnableScheduledFuture<Void> t = decorateTask(command, sft);
    16    sft.outerTask = t;
    17    //添加任务到队列
    18    delayedExecute(t);
    19    return t;
    20 }

    如果是周期任务则执行上面run()方法中的第12行,调用父类中的runAndReset(),这个方法同run方法比较的区别是call方法执行后不设置结果,因为周期型任务会多次执行,所以为了让FutureTask支持这个特性除了发生异常不设置结果。

    执行完任务后通过setNextRunTime方法计算下一次启动时间:

     1 private void setNextRunTime() {
     2    long p = period;
     3   //period=delay;
     4    if (p > 0)
     5        time += p;//由于period>0所以执行这里,设置time=time+delay
     6    else
     7        time = triggerTime(-p);
     8 }
     9 
    10 long triggerTime(long delay) {
    11     return now() +
    12         ((delay < (Long.MAX_VALUE >> 1)) ? delay : overflowFree(delay));
    13 }

    scheduleAtFixedRate会执行到情况一,下一次任务的启动时间最早为上一次任务的启动时间加period。
    scheduleWithFixedDelay会执行到情况二,这里很巧妙的将period参数设置为负数到达这段代码块,在此又将负的period转为正数。情况二将下一次任务的启动时间设置为当前时间加period。

    然后将任务再次添加到任务队列:

     1 /**
     2  * 重新执行任务task
     3  */
     4 void reExecutePeriodic(RunnableScheduledFuture<?> task) {
     5     // 判断当前线程池状态能不能运行任务
     6     if (canRunInCurrentRunState(true)) {
     7         // 将任务添加到任务队列,会根据任务延时时间进行排序
     8         super.getQueue().add(task);
     9         // 如果线程池状态改变了,当前状态不能运行任务,那么就尝试移除任务,
    10         // 移除成功,就取消任务。
    11         if (!canRunInCurrentRunState(true) && remove(task))
    12             task.cancel(false);
    13         else
    14             // 预先启动工作线程,确保线程池中有工作线程。
    15             ensurePrestart();
    16     }
    17 }

    这个方法与delayedExecute方法很像,都是将任务添加到任务队列中。

    1. 如果当前线程池状态能够运行任务,那么任务添加到任务队列。
    2. 如果在在添加任务的过程中,线程池状态是否被别的线程更改了,那么就要进行判断,是否需要取消任务。
    3. 调用ensurePrestart()方法,预先启动工作线程,确保线程池中有工作线程。

    ScheduledFuture的get方法

    既然ScheduledFuture的实现是ScheduledFutureTask,而ScheduledFutureTask继承自FutureTask,所以ScheduledFuture的get方法的实现就是FutureTask的get方法的实现,FutureTask的get方法的实现分析在ThreadPoolExecutor篇已经写过,这里不再叙述。要注意的是ScheduledFuture的get方法对于非周期任务才是有效的。

    ScheduledThreadPoolExecutor总结

    • ScheduledThreadPoolExecutor和ThreadPoolExecutor的区别:

        ThreadPoolExecutor每次addwoker就会将自己的Task传进新创建的woker中的线程执行,因此woker会第一时间执行当前Task,只有线程数超过了核心线程才会将任务放进队列里

        ScheduledThreadPoolExecutor是直接入队列,并且创建woker时传到woker的是null,说明woker中的线程刚启动时并没有任务执行,只能通过getTask去队列里取任务,取任务时会判断是否到了执行时间,因此具有了延时执行的特性,并且task执行完了,会将当前任务重新放进堆里,并设置下次执行的时间。

    • ScheduledThreadPoolExecutor是实现自ThreadPoolExecutor的线程池,构造方法中传入参数n,则最多会有n个核心线程工作,空闲的核心线程不会被自动终止,而是一直阻塞在DelayedWorkQueue的take方法尝试获取任务。构造方法传入的参数为0,ScheduledThreadPoolExecutor将以非核心线程工作,并且最多只会创建一个非核心线程,参考上文中ensurePrestart方法的执行过程。而这个非核心线程以poll方法获取定时任务之所以不会因为超时就被回收,是因为任务队列并不为空,只有在任务队列为空时才会将空闲线程回收,详见ThreadPoolExecutor篇的runWorker方法,之前我以为空闲的非核心线程超时就会被回收是不正确的,还要具备任务队列为空这个条件。

    • ScheduledThreadPoolExecutor的定时执行任务依赖于DelayedWorkQueue,其内部用可扩容的数组实现以启动时间升序的二叉树。

    • 工作线程尝试获取DelayedWorkQueue的任务只有在任务到达指定时间才会成功,否则非核心线程会超时返回null,核心线程一直阻塞。

    • 对于非周期型任务只会执行一次并且可以通过ScheduledFuture的get方法阻塞得到结果,其内部实现依赖于FutureTask的get方法。

    • 周期型任务通过get方法无法获取有效结果,因为FutureTask对于周期型任务执行的是runAndReset方法,并不会设置结果。周期型任务执行完毕后会重新计算下一次启动时间并且再次添加到DelayedWorkQueue中,所有的Task会公用一个队列,如果一个定时器里添加多个任务,此时就会形成堆,如果只是一个定时任务,则每次只有堆顶一个数据,并且也只需要一个核心线程就够用了,因为只有当前任务执行完才会再将该任务添加到堆里。

  • 相关阅读:
    centos 7 install
    sbt
    maven create project
    java异常个人理解
    (poj1094)Sorting It All Out
    stars
    Following Orders(拓扑排序)
    The House Of Santa Claus(dfs)
    Prime Path(bfs)
    Fence Repair(优先队列容器的应用)
  • 原文地址:https://www.cnblogs.com/java-chen-hao/p/10283413.html
Copyright © 2020-2023  润新知