深度解析ScheduledThreadPoolExecutor源码之ScheduledFutureTask

本文主要是介绍深度解析ScheduledThreadPoolExecutor源码之ScheduledFutureTask,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 引言
  • 一、RunnableScheduledFuture定义周期性接口
  • 二、ScheduledFutureTask源码分析
    • 2.1 ScheduledFutureTask参数解析
    • 2.2 ScheduledFutureTask源码方法解析
  • 总结


引言

在上一章节我们已经对ScheduledThreadPoolExecutor中的延迟队列DelayedWorkQueue做了源码分析深度解析ScheduledThreadPoolExecutor源码之DelayedWorkQueue,接下来我们将对ScheduledThreadPoolExecutor中的另一个核心类ScheduledFutureTask做源码讲解。在阅读此文章之前,希望您已经掌握了关于JDK中关于FutureFutureTask相关的知识点,如果您对FutureTask的原理还不太了解,您可以先阅读文章Java中的Future源码讲解进行初步了解。


一、RunnableScheduledFuture定义周期性接口

ScheduledFutureTask继承FutureTask标志着该类可以作为Runnable接口的实现类放入Thread线程中运行,关于FutureTask的源码解析可参考文章Future源码讲解,在此处就不再过多解析。ScheduledFutureTask除了继承FutureTask之外,还实现了接口
RunnableScheduledFuture,因为仅仅继承FutureTask类只能保证ScheduledFutureTask可以放在Thread中运行,但是并无法标识该任务是一次性任务还是周期性任务还是延迟任务。因此RunnableScheduledFuture接口提供了方法isPeriodic来让其实现类标识是否是周期性任务。
在这里插入图片描述
因此ScheduledFutureTask只需要实现RunnableScheduledFuture接口中的isPeriodic方法,通过返回的布尔值即可标识当前任务是否是周期性任务。现在解决了标识一个任务是否是周期性任务的问题,另一个问题则是如何计算任务应该何时运行,ScheduledFutureRunnableScheduledFuture所继承,在ScheduledFuture又继承了Delayed接口,Delayed接口定义了一个方法getDelay用于返回待延迟执行的时间戳值。规定如果getDelay返回的值大于0,则表示该任务还未到执行时间,如果等于或者小于0,则表示当前任务可以被执行。

/*** A delayed result-bearing action that can be cancelled.* Usually a scheduled future is the result of scheduling* a task with a {@link ScheduledExecutorService}.** @since 1.5* @author Doug Lea* @param <V> The result type returned by this Future*/
public interface ScheduledFuture<V> extends Delayed, Future<V> {
}

请注意看,Delayed 继承了Comparable接口,因此实现该接口的类,可以通过实现Comparable提供的compareTo方法进行比较,根据两个任务调用getDelay方法返回值来判断哪个任务优先执行,结合DelayedWorkQueue的最小堆算法,则可以保证任务执行的顺序

public interface Delayed extends Comparable<Delayed> {/*** Returns the remaining delay associated with this object, in the* given time unit.** @param unit the time unit* @return the remaining delay; zero or negative values indicate* that the delay has already elapsed* 返回还剩多长时间任务才能被允许执行* 如果返回0或者负数,表示任务已经过期,应该马上被执行* 实现该接口的元素,会根据延迟时间的顺序被放入队列(PriorityQueue),时间越短则越靠近对头*/long getDelay(TimeUnit unit);
}
public interface Comparable<T> {public int compareTo(T o);
}

至此,对ScheduledFutureTask大体的实现流程有一定的概念,即:ScheduledFutureTask继承FutureTask以至于可以作为Runnable接口实现类放入Thread中运行,实现接口RunnableScheduledFutureisPeriodic方法可以确定任务的类型(定时周期性、延迟周期性、一次性任务),实现接口DelayedgetDelay方法,计算出任务可执行的时间,接着通过实现Comparable提供的compareTo方法,将两个任务通过getDelay方法返回的值进行比较,getDelay返回的数值越小,则任务越应该尽早的执行,有了比较的依据,则通过最小堆算法放入DelayedWorkQueue中,等待线程获取任务执行。以上就是ScheduledFutureTask实现的大体流程,接下来我们将结合代码进一步仔细分析。

二、ScheduledFutureTask源码分析

ScheduledFutureTask作为FutureTask的增强类,增加了getDelay方法用于计算任务当前时间是否应该被执行,compareTo方法则用于比较多个任务之间执行顺序排序。isPeriodic方法则可以标识当前任务是否是周期性任务。接下来我们进入JDK源码从上往下依次解析每个参数与方法的具体作用和实现。
在这里插入图片描述

2.1 ScheduledFutureTask参数解析

ScheduledFutureTask中定义的参数比较少,sequenceNumber是相当于一个任务的身份标识,当创建一个任务(ScheduledFutureTask)时会为任务的属性sequenceNumber赋值一个唯一值用于区分任务。time参数用于记录任务何时应该执行的时间戳。period则是用于标识一个任务的类型(周期性、延迟任务、单次任务)。outerTask则是记录任务本身,如果任务是周期性的时候,执行完一次任务后可以将该任务重新放入DelayedWorkQueue中。heapIndex则是记录当前任务在二叉堆中的索引位置,记录该位置是为了更方便通过索引查到任务。关于二叉堆的相关知识点,我们已经在上一章节讲过,请参考文章深度解析ScheduledThreadPoolExecutor源码之DelayedWorkQueue

/*** Sequence number to break ties FIFO 分配的唯一序列号*/private final long sequenceNumber;/*** The time the task is enabled to execute in nanoTime units* 执行任务的时间*/private long time;/*** Period in nanoseconds for repeating tasks.  A positive* value indicates fixed-rate execution.  A negative value* indicates fixed-delay execution.  A value of 0 indicates a* non-repeating task.* 一个积极的取值表示固定速率执行。* 负值固定延迟执行。* 0表示无重复的任务* 重复任务的周期,以纳秒为单位 表示是否周期性任务,还是延迟任务*/private final long period;/*** The actual task to be re-enqueued by reExecutePeriodic* ScheduledFutureTask对象,实际指向当前对象本身*/RunnableScheduledFuture<V> outerTask = this;/*** Index into delay queue, to support faster cancellation.* 当前任务在延迟队列中的索引*/int heapIndex;

2.2 ScheduledFutureTask源码方法解析

ScheduledFutureTask中方法并不多,根据这些方法的名称大概能猜测每个方法的作用,接下来我们将通过源码对每个方法进行解析。
在这里插入图片描述
ScheduledFutureTask 提供了三个构造方法,super(r, result)则是调用父类FutureTask将传入的Runnableresult构建成一个Callable并初始化任务状态为NEW如果您对FutureTask的实现还不太清除,您可以参考文章 Future源码讲解。将传入的执行时间戳赋值给time,因为time用于记录任务何时执行,period 则是标识该任务是否为周期性任务,如果不传则默认为0,表示当前任务是周期性任务。第三个构造函数允许您传入自己定义的callable,这样就不再由FutureTask自动为你构建callable

/*** Creates a one-shot action with given nanoTime-based trigger time.* 传入Runnable和一个固定的返回值,并指定任务执行的时间*/ScheduledFutureTask(Runnable r, V result, long ns) {super(r, result);this.time = ns;this.period = 0;//表示是一个单次任务this.sequenceNumber = sequencer.getAndIncrement( );}/*** Creates a periodic action with given nano time and period.* 创建具有给定纳米时间和周期的周期性动作。*/ScheduledFutureTask(Runnable r, V result, long ns, long period) {super(r, result);this.time = ns;this.period = period;this.sequenceNumber = sequencer.getAndIncrement();}/*** Creates a one-shot action with given nanoTime-based trigger time.* 使用给定的基于nanotime的触发时间创建一个一次性动作。*/ScheduledFutureTask(Callable<V> callable, long ns) {super(callable);this.time = ns;this.period = 0;this.sequenceNumber = sequencer.getAndIncrement();}

getDelay方法来源于Delayed接口,getDelay方法返回值用于确定任务是否到了可执行状态,如果返回值大于0,则表示任务还需要等待返回值的时间才能执行,如果小于等于0,则表示任务可以被执行。time在参数讲解的时候提到过,time记录着该任务可被执行的时间,time - now() 则返回可执行时间于当前时间的差值,从而判断任务是否到了可执行时间。

/*** 获取当前任务延迟执行的时间** @param unit* @return*/public long getDelay(TimeUnit unit) {return unit.convert(time - now(), NANOSECONDS);//time记录着任务应该被执行的时间,time-now()==等于距离可执行时间的差值}

isPeriodic方法来源于接口RunnableScheduledFuture,用于定义一个任务的类型(周期性一次性),通过参数period 是否为0来确定是否为周期性任务。在进行ScheduledFutureTask构建时,可由用户自己定义任务类型。

/*** Returns {@code true} if this is a periodic (not a one-shot) action.*如果这是一个周期性的(不是一次性的)动作,返回{@code true}。* @return {@code true} if periodic*/public boolean isPeriodic() {return period != 0;}

setNextRunTime方法用于计算出任务下一次执行时间,如果period为0,则表示任务为周期性,则可以计算出下一次执行时间并赋值给time参数,如果为负数,则表示任务是延迟任务,只需要调用triggerTime方法计算出延迟后任务的执行时间即可。

/*** Sets the next time to run for a periodic task.* 为周期性任务设置下一次调度时间*/private void setNextRunTime() {long p = period;if (p > 0) { //如果p大于0,则表示当前任务是一个固定速率的任务,只需要在本次任务执行的时间+p则可以算出下次任务执行的时间time += p;}else { //延迟动作的触发时间time = triggerTime(-p);}}

cancel方法用于取消任务,调用父类FutureTask中的cancel方法将任务状态置为取消,并调用remove方法将该任务从DelayedWorkQueue中移除。

/*** 设置是否可以在任务运行时进行任务取消* @param mayInterruptIfRunning* @return*/public boolean cancel(boolean mayInterruptIfRunning) {boolean cancelled = super.cancel(mayInterruptIfRunning);if (cancelled && removeOnCancel && heapIndex >= 0)remove(this);return cancelled;}

compareTo方法来源于接口Comparable,用于比较两个任务的顺序,离执行时间越近的越先执行。如果任务类型是ScheduledFutureTask,则先通过参数time比较两个任务谁先执行,如果两个任务的执行时间一直,则比较sequenceNumber ,一般来说任务越先创建,sequenceNumber就越小。如果任务非ScheduledFutureTask类型,则通过getDelay方法比较谁离执行时间更近。

/*** 任务比较,用于在插入和出列是进行排序** @param other* @return*/public int compareTo(Delayed other) {if (other == this) // compare zero if same objectreturn 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;//此处的比较是,当前两个任务的执行时间一致时(time都一致),则根据sequenceNumber比较,sequenceNumber是在创建任务时设置的// sequenceNumber越小,则任务越先创建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;}

run方法来源于Rnnable接口,是任务执行的最核心代码。ScheduledFutureTask重写了FutureTask中的run方法,因此任务执行时,正在执行的代码就是这个方法。首先通过方法isPeriodic判断任务是否为周期性任务,然后通过canRunInCurrentRunState判断任务是否能被执行,如果当前状态无法被执行,则调用cancel方法取消任务。如果任务为非周期性任务,则直接调用父类ScheduledFutureTask.super.run允许任务,如果是周期性任务,则调用cheduledFutureTask.super.runAndReset(),方法runAndReset可以允许任务并在允许完成后将任务状态重置,以此来实现任务的周期重复调用。执行完成后计算出任务下一次执行时间,调用reExecutePeriodic方法尝试将任务重新放入DelayedWorkQueue中等待执行。任务放入DelayedWorkQueue就是调用DelayedWorkQueue的offer方法,这样ScheduledFutureTask与DelayedWorkQueue就关联起来。

/*** Overrides FutureTask version so as to reset/requeue if periodic.* 覆盖FutureTask版本,以便定期重置/重新请求。* 任务允许的真正入口*/public void run() {boolean periodic = isPeriodic();//是否是周期性任务if (!canRunInCurrentRunState(periodic)) { //判断当前任务是否能被允许,查看线程池状态cancel(false);//如果当前任务不能被允许,则尝试取消该任务,中断当前线程}else if (!periodic) {ScheduledFutureTask.super.run(); //如果当前任务非周期性任务,则直接调用一次run方法后结束}else if (ScheduledFutureTask.super.runAndReset()) { //如果是周期性任务,则调用runAndReset方法运行任务,重置任务状态setNextRunTime();//任务完成后计算该任务下一次运行时间reExecutePeriodic(outerTask);//尝试将该任务重新放入延迟队列中等待下次继续运行}}

总结

通过本章节对ScheduledFutureTask的解析,再结合上文深度解析ScheduledThreadPoolExecutor源码之DelayedWorkQueue,我们已经将ScheduledThreadPoolExecutor中两个核心的类进行了全方位解析。剩余的内容则是对ScheduledThreadPoolExecutor内部参数与方法的解析。我们打算将这一部分放到第三章节去讲解。

这篇关于深度解析ScheduledThreadPoolExecutor源码之ScheduledFutureTask的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/681630

相关文章

网页解析 lxml 库--实战

lxml库使用流程 lxml 是 Python 的第三方解析库,完全使用 Python 语言编写,它对 XPath表达式提供了良好的支 持,因此能够了高效地解析 HTML/XML 文档。本节讲解如何通过 lxml 库解析 HTML 文档。 pip install lxml lxm| 库提供了一个 etree 模块,该模块专门用来解析 HTML/XML 文档,下面来介绍一下 lxml 库

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

如何在Visual Studio中调试.NET源码

今天偶然在看别人代码时,发现在他的代码里使用了Any判断List<T>是否为空。 我一般的做法是先判断是否为null,再判断Count。 看了一下Count的源码如下: 1 [__DynamicallyInvokable]2 public int Count3 {4 [__DynamicallyInvokable]5 get

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、

基于UE5和ROS2的激光雷达+深度RGBD相机小车的仿真指南(五):Blender锥桶建模

前言 本系列教程旨在使用UE5配置一个具备激光雷达+深度摄像机的仿真小车,并使用通过跨平台的方式进行ROS2和UE5仿真的通讯,达到小车自主导航的目的。本教程默认有ROS2导航及其gazebo仿真相关方面基础,Nav2相关的学习教程可以参考本人的其他博客Nav2代价地图实现和原理–Nav2源码解读之CostMap2D(上)-CSDN博客往期教程: 第一期:基于UE5和ROS2的激光雷达+深度RG

韦季李输入法_输入法和鼠标的深度融合

在数字化输入的新纪元,传统键盘输入方式正悄然进化。以往,面对实体键盘,我们常需目光游离于屏幕与键盘之间,以确认指尖下的精准位置。而屏幕键盘虽直观可见,却常因占据屏幕空间,迫使我们在操作与视野间做出妥协,频繁调整布局以兼顾输入与界面浏览。 幸而,韦季李输入法的横空出世,彻底颠覆了这一现状。它不仅对输入界面进行了革命性的重构,更巧妙地将鼠标这一传统外设融入其中,开创了一种前所未有的交互体验。 想象

OWASP十大安全漏洞解析

OWASP(开放式Web应用程序安全项目)发布的“十大安全漏洞”列表是Web应用程序安全领域的权威指南,它总结了Web应用程序中最常见、最危险的安全隐患。以下是对OWASP十大安全漏洞的详细解析: 1. 注入漏洞(Injection) 描述:攻击者通过在应用程序的输入数据中插入恶意代码,从而控制应用程序的行为。常见的注入类型包括SQL注入、OS命令注入、LDAP注入等。 影响:可能导致数据泄

从状态管理到性能优化:全面解析 Android Compose

文章目录 引言一、Android Compose基本概念1.1 什么是Android Compose?1.2 Compose的优势1.3 如何在项目中使用Compose 二、Compose中的状态管理2.1 状态管理的重要性2.2 Compose中的状态和数据流2.3 使用State和MutableState处理状态2.4 通过ViewModel进行状态管理 三、Compose中的列表和滚动