ScheduleThreadPoolExecutor源码分析

ScheduleThreadPoolExecutor源码分析(一)

Java中ScheduleThreadPoolExecutor主要用于执行延迟任务或者按照一定的频率执行任务。其中scheduleAtFixedRate函数是按照一定频率执行任务,scheduleWithFixedDelay可以根据延迟一定时间再执行任务。本文将参考ScheduleThreadPoolExecutor的源码来剖析其为什么能够支持延迟并按照固定频率执行任务。

ScheduleThreadPoolExecutor之所以能够延迟并按照一定频率执行任务主要依靠其内部封装的两个内部类,ScheduledFutureTask和DelayedWorkQueue。其中ScheduledFutureTask继承了FutureTask类,因此其可以封装继承了Runable或Callable接口的任务。而DelayedWorkQueue则为一个延迟队列,其利用最小堆实现,需要首先执行的任务在堆顶,这样每次执行任务时只需要获取堆顶的任务即可。

ScheduledFutureTask:

ScheduledFutureTask继承了FutureTask,因此其能够被ScheduledExecutorService执行。下面看一下ScheduledFutureTask的一些重要属性:

  • int heapIndex:表示改任务在DelayedWorkQueue队列中的索引。由于DelayedWorkQueue是由最小堆构建的,为了提高查找的速度,在封装的Task里引入该字段,可以使查找时间复杂度降到O(1)。
  • private long time:表示该任务执行的时间,在getDelay()函数中根据unit.convert(time - now(), NANOSECONDS)来获得任务还需要多长时间才能执行。同时DelayedWorkQueue中也是根据该字段来维护最小堆的。
  • private final long period:执行重复任务的时间。正数表示按照一定的速率执行任务,负数表示按照一定的延迟执行任务,0表示任务不重复执行。
  • RunnableScheduledFuture outerTask = this:指定该任务。
  • private final long sequenceNumber:任务进入队列的顺序,保证队列的FIFO

对于ScheduledFutureTask的方法,最主要的是compareTo和getDelay和setNextRunTime方法。

  •   public long getDelay(TimeUnit unit) {
        return unit.convert(time - now(), NANOSECONDS);
    }

    该方法主要是用来获得任务需要执行时的延迟时间,其在DelayedWorkQueue中的offer函数中有重要的运用。

    public int compareTo(Delayed other) {
        if (other == this) // compare zero if same object
            return 0;
        if (other instanceof ScheduledFutureTask) {
            ScheduledFutureTask<?> x = (ScheduledFutureTask<?>)other;
            long diff = time - x.time;
            if (diff < 0)
                return -1;
            else if (diff > 0)
                return 1;
            else if (sequenceNumber < x.sequenceNumber)
                return -1;
            else
                return 1;
        }
        long diff = getDelay(NANOSECONDS) - other.getDelay(NANOSECONDS);
        return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;
    }

    compareTo方法主要用于DelayQueue中最小堆的排序,其首先根据任务执行时间来判断,如果任务执行时间相同则按照队列的FIFO规则进行判断。

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

    setNextRunTIme()方法主要是为需要重复执行的任务设置下次执行的时间,当period > 0时表示任务是按照一定速率执行的,只需要将本次执行时间加上间隔时间即可。当period < 0时表示任务是延期执行的,需要调用triggerTime来获得下次执行时间。下面是triggerTime函数的实现:

    long triggerTime(long delay) {
    return now() +
        ((delay < (Long.MAX_VALUE >> 1)) ? delay : overflowFree(delay));
    }
    
    /**
    * Constrains the values of all delays in the queue to be within
     * Long.MAX_VALUE of each other, to avoid overflow in compareTo.
    * This may occur if a task is eligible to be dequeued, but has
    * not yet been, while some other task is added with a delay of
    * Long.MAX_VALUE.
    */
    private long overflowFree(long delay) {
    Delayed head = (Delayed) super.getQueue().peek();
    if (head != null) {
        long headDelay = head.getDelay(NANOSECONDS);
        if (headDelay < 0 && (delay - headDelay < 0))
            delay = Long.MAX_VALUE + headDelay;
        }
        return delay;
    }

    由上面的代码可知,对于延迟执行的任务,执行时间是当前时间加上延迟时间。而为了防止在conpareTo进行比较时数值过大,延迟时间又是根据队列中下一个要执行的任务的执行时间来获得。下一篇讲介绍DelayQueue的详细实现。

时间: 2024-10-14 21:10:45

ScheduleThreadPoolExecutor源码分析的相关文章

TeamTalk源码分析之login_server

login_server是TeamTalk的登录服务器,负责分配一个负载较小的MsgServer给客户端使用,按照新版TeamTalk完整部署教程来配置的话,login_server的服务端口就是8080,客户端登录服务器地址配置如下(这里是win版本客户端): 1.login_server启动流程 login_server的启动是从login_server.cpp中的main函数开始的,login_server.cpp所在工程路径为server\src\login_server.下表是logi

Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)

1 背景 还记得前一篇<Android触摸屏事件派发机制详解与源码分析一(View篇)>中关于透过源码继续进阶实例验证模块中存在的点击Button却触发了LinearLayout的事件疑惑吗?当时说了,在那一篇咱们只讨论View的触摸事件派发机制,这个疑惑留在了这一篇解释,也就是ViewGroup的事件派发机制. PS:阅读本篇前建议先查看前一篇<Android触摸屏事件派发机制详解与源码分析一(View篇)>,这一篇承接上一篇. 关于View与ViewGroup的区别在前一篇的A

HashMap与TreeMap源码分析

1. 引言     在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Java这么久,也写过一些小项目,也使用过TreeMap无数次,但到现在才明白它的实现原理).因此本着"不要重复造轮子"的思想,就用这篇博客来记录分析TreeMap源码的过程,也顺便瞅一瞅HashMap. 2. 继承结构 (1) 继承结构 下面是HashMap与TreeMap的继承结构: pu

Linux内核源码分析--内核启动之(5)Image内核启动(rest_init函数)(Linux-3.0 ARMv7)【转】

原文地址:Linux内核源码分析--内核启动之(5)Image内核启动(rest_init函数)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.chinaunix.net/uid-25909619-id-4938395.html 前面粗略分析start_kernel函数,此函数中基本上是对内存管理和各子系统的数据结构初始化.在内核初始化函数start_kernel执行到最后,就是调用rest_init函数,这个函数的主要使命就是创建并启动内核线

Spark的Master和Worker集群启动的源码分析

基于spark1.3.1的源码进行分析 spark master启动源码分析 1.在start-master.sh调用master的main方法,main方法调用 def main(argStrings: Array[String]) { SignalLogger.register(log) val conf = new SparkConf val args = new MasterArguments(argStrings, conf) val (actorSystem, _, _, _) =

Solr4.8.0源码分析(22)之 SolrCloud的Recovery策略(三)

Solr4.8.0源码分析(22)之 SolrCloud的Recovery策略(三) 本文是SolrCloud的Recovery策略系列的第三篇文章,前面两篇主要介绍了Recovery的总体流程,以及PeerSync策略.本文以及后续的文章将重点介绍Replication策略.Replication策略不但可以在SolrCloud中起到leader到replica的数据同步,也可以在用多个单独的Solr来实现主从同步.本文先介绍在SolrCloud的leader到replica的数据同步,下一篇

zg手册 之 python2.7.7源码分析(4)-- pyc字节码文件

什么是字节码 python解释器在执行python脚本文件时,对文件中的python源代码进行编译,编译的结果就是byte code(字节码) python虚拟机执行编译好的字节码,完成程序的运行 python会为导入的模块创建字节码文件 字节码文件的创建过程 当a.py依赖b.py时,如在a.py中import b python先检查是否有b.pyc文件(字节码文件),如果有,并且修改时间比b.py晚,就直接调用b.pyc 否则编译b.py生成b.pyc,然后加载新生成的字节码文件 字节码对象

LevelDB源码分析--Iterator

我们先来参考来至使用Iterator简化代码2-TwoLevelIterator的例子,略微修改希望能帮助更加容易立即,如果有不理解请各位看客阅读原文. 下面我们再来看一个例子,我们为一个书店写程序,书店里有许多书Book,每个书架(BookShelf)上有多本书. 类结构如下所示 class Book { private: string book_name_; }; class Shelf { private: vector<Book> books_; }; 如何遍历书架上所有的书呢?一种实

【Heritrix源码分析】Heritrix基本内容介绍

1.版本说明 (1)最新版本:3.3.0 (2)最新release版本:3.2.0 (3)重要历史版本:1.14.4 3.1.0及之前的版本:http://sourceforge.net/projects/archive-crawler/files/ 3.2.0及之后的版本:http://archive.org/ 由于国情需要,后者无法访问,因此本blog研究的是1.14.4版本. 2.官方材料 source:http://sourceforge.net/projects/archive-cra