当前位置: 首页 > news >正文

Java Executor ScheduledThreadPoolExecutor 总结

前言


 相关系列

  • 《Java & Executor & 目录》
  • 《Java & Executor & ScheduledThreadPoolExecutor & 源码》
  • 《Java & Executor & ScheduledThreadPoolExecutor & 总结》
  • 《Java & Executor & ScheduledThreadPoolExecutor & 问题》
     
     

 涉及内容

  • 《Java & Executor & 总结》
  • 《Java & Executor & ExecutorService & 源码》
  • 《Java & Executor & ScheduledExecutorService & 总结》
  • 《Java & Executor & ThreadPoolExecutor & 总结》
  • 《Java & Executor & Future & 总结》
  • 《Java & Executor & FutureTask & 总结》
  • 《Java & Executor & ScheduledFutureTask & 总结》
  • 《Java & Collection/Executor & BlockingQueue & 总结》
  • 《Java & Collection/Executor & DelayedWorkQueue & 总结》
  • 《Java & Executor & Callable & 总结》
  • 《Java & Thread & Runnable & 总结》
     
     

概述


 简介

在这里插入图片描述

    ScheduledThreadPoolExecutor @ 调度线程池执行器类具备并发调度(延迟/周期)执行任务的能力。调度线程池执行器类是ThreadPoolExecutor @ 线程池执行器类/ScheduledExecutorService @ 调度执行器服务接口的子/实现类,在继承了多线程并发执行任务能力的同时还通过定制/组合延迟队列及封装任务的方式实现了令任务调度执行的效果。
 
 

使用


 创建

  • public ScheduledThreadPoolExecutor(int corePoolSize) —— 创建指定核心池大小,且最大池大小为Integer.MAX_VALUE/持续存活时间为0/时间单位为纳秒/工作队列为延迟工作队列/线程工厂为默认线程工厂/拒绝执行处理器为中断策略的调度线程池执行器。

  • public ScheduledThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory) —— 创建指定核心池大小/线程工厂,且最大池大小为Integer.MAX_VALUE/持续存活时间为0/时间单位为纳秒/工作队列为延迟工作队列/拒绝执行处理器为中断策略的调度线程池执行器。

  • public ScheduledThreadPoolExecutor(int corePoolSize, RejectedExecutionHandler handler) —— 创建指定核心池大小/拒绝执行处理器,且最大池大小为Integer.MAX_VALUE/持续存活时间为0/时间单位为纳秒/工作队列为延迟工作队列/线程工厂为默认线程工厂的调度线程池执行器。

  • public ScheduledThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory, RejectedExecutionHandler handler) —— 创建指定核心池大小/线程工厂/拒绝执行处理器,且最大池大小为Integer.MAX_VALUE/持续存活时间为0/时间单位为纳秒/工作队列为延迟工作队列的调度线程池执行器。
     

 调度/执行/递交

  • public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) —— 调度 —— 向当前调度线程池执行器递交可在指定延迟后执行的指定可运行任务,并返回可追踪/干预/获取指定可运行任务执行状态/过程/结果的调度未来。该方法无需在递交指定可运行任务时同步传入承载变量以作为向调度未来传递执行结果的媒介,因此调度未来无法用于获取指定可运行任务的执行结果,即调度未来的get(…)方法将永远返回null。

  • public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) —— 调度 —— 向当前调度线程池执行器递交可在指定延迟后执行的指定可调用任务,并返回可追踪/干预/获取指定可调用任务执行状态/过程/结果的调度未来。

  • public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) —— 按固定速率调度 —— 向当前调度线程池执行器递交可在指定初始延迟后按指定周期周期性执行的指定可运行任务,并返回可追踪/干预/获取指定可运行任务执行状态/过程/结果的调度未来。该方法无需在递交指定可运行任务时同步传入承载变量以作为向调度未来传递执行结果的媒介,因此调度未来无法用于获取指定可运行任务的执行结果,即调度未来的get(…)方法将永远返回null。但又因为指定可运行任务会在未取消/无异常的情况下无限周期性执行而不记录结果,因此调度未来的get(…)方法要么因为取消/异常/超时而抛出取消/执行/超时异常,要么因为无法获取结果而无限等待。此外如果指定可运行任务的执行时间超过周期,那么下次执行会延迟至当前执行结束时启动,因此不会出现并发执行的情况。

  • public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) —— 随固定延迟调度 —— 向当前调度线程池执行器递交可在指定初始延迟后按指定延迟周期性执行的指定可运行任务,并返回可追踪/干预/获取指定可运行任务执行状态/过程/结果的调度未来。该方法无需在递交指定可运行任务时同步传入承载变量以作为向调度未来传递执行结果的媒介,因此调度未来无法用于获取指定可运行任务的执行结果,即调度未来的get(…)方法将永远返回null。但又因为指定可运行任务会在未取消/无异常的情况下无限周期性执行而不记录结果,因此调度未来的get(…)方法要么因为取消/异常/超时而抛出取消/执行/超时异常,要么因为无法获取结果而无限等待。

  • public void execute(Runnable command) —— 执行 —— 向当前调度线程池执行器递交可立即执行的指定可运行任务。该方法等价于无/负延迟调用schedule(Runnable command, long delay, TimeUnit unit)方法。

  • public Future submit(Callable task) —— 递交 —— 向当前调度线程池执行器递交可立即执行的指定可调用任务,并返回可追踪/干预/获取指定可调用任务执行状态/过程/结果的未来。该方法等价于无/负延迟调用schedule(Callable callable, long delay, TimeUnit unit)方法。

  • public Future<?> submit(Runnable task) —— 递交 —— 向当前调度线程池执行器递交可立即执行的指定可运行任务,并返回可追踪/干预/获取指定可运行任务执行状态/过程/结果的未来。该方法无需在递交指定可运行任务时同步传入承载变量以作为向未来传递执行结果的媒介,因此未来无法用于获取指定可运行任务的执行结果,即未来的get(…)方法将永远返回null。该方法等价于无/负延迟调用schedule(Runnable command, long delay, TimeUnit unit)方法。

  • public Future submit(Runnable task, T result) —— 递交 —— 向当前调度线程池执行器递交可立即执行的指定可运行任务,并返回可追踪/干预/获取指定可运行任务执行状态/过程/结果的未来。该方法需在递交指定可运行任务时同步传入承载变量以作为向未来传递执行结果的媒介,因此承载变量/未来皆可用于获取指定可运行任务的执行结果。该方法等价于无/负延迟调用schedule(Callable callable, long delay, TimeUnit unit)方法。

  • public List<Future> invokeAll(Collection<? extends Callable> tasks) throws InterruptedException —— 调用所有 —— 向当前调度线程池执行器递交可立即执行的指定可调用任务集,并返回可追踪/干预/获取指定可调用任务执行状态/过程/结果的未来集。该方法会无限等待至指定可调用任务集全部执行结束,因此未来集中每个未来的isDone()方法调用结果都为true。当前线程在进入方法/等待执行期间如果已/被中断会抛出中断异常,但中断状态会被清除。

  • public List<Future> invokeAll(Collection<? extends Callable> tasks, long timeout, TimeUnit unit) throws InterruptedException —— 调用所有 —— 向当前调度线程池执行器递交可立即执行的指定可调用任务集,并返回可追踪/干预/获取指定可调用任务执行状态/过程/结果的未来集。该方法会有限等待至指定可调用任务集全部执行结束,超出指定等待时间则取消剩余未结束的可调用任务,因此未来集中每个未来的isDone()方法调用结果都为true。当前线程在进入方法/等待执行期间如果已/被中断会抛出中断异常,但中断状态会被清除。

  • public T invokeAny(Collection<? extends Callable> tasks) throws InterruptedException, ExecutionException —— 调用任意 —— 向当前调度线程池执行器递交指定可调用任务集,并返回其中一个已执行完成的可调用任务执行结果。该方法会无限等待至指定可调用任务集中的一个执行完成,并取消剩余未结束的可调用任务。而如果所有可调用任务都执行失败(异常/取消)则会抛出执行异常。当前线程在进入方法/等待执行期间如果已/被中断会抛出中断异常,但中断状态会被清除。

  • public T invokeAny(Collection<? extends Callable> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException —— 调用任意 —— 向当前调度线程池执行器递交指定可调用任务集,并返回其中一个已执行完成的可调用任务执行结果。该方法会有限等待至指定可调用任务集中的一个执行完成,并取消剩余未结束的可调用任务。超出指定等待时间则取消剩余未结束的可调用任务并抛出超时异常。而如果未超时但所有可调用任务都执行失败则会抛出执行异常。当前线程在进入方法/等待执行期间如果已/被中断会抛出中断异常,但中断状态会被清除。

    invokeAll(…)/invokeAny(…)方法应在调度线程池执行器中不存在(大量)等待中任务时执行。由于invokeAll(…)/invokeAny(…)方法会等待至指定可调用任务集中的全部/一个执行结束,因此调度线程池执行器中存在大量等待中任务将可能导致invokeAll(…)/invokeAny(…)方法陷入长时间的等待。此外由于与早期任务并发执行还可能影响invokeAll(…)/invokeAny(…)方法的执行效率,因此在具体执行invokeAll(…)/invokeAny(…)方法前应该进行等待中任务的数量判断。
 

 设置

  • public void setCorePoolSize(int corePoolSize) —— 设置核心池大小 —— 设置当前调度线程池执行器的核心池大小。

  • public void allowCoreThreadTimeOut(boolean value) —— 允许核心线程超时 —— 设置当前调度线程池执行器的核心执行线程是否允许超时回收。

  • public boolean allowsCoreThreadTimeOut() —— 允许核心线程超时 —— 判断当前调度线程池执行器是否允许核心执行线程超时回收。

  • public void setMaximumPoolSize(int maximumPoolSize) —— 设置最大池大小 —— 设置当前调度线程池执行器的最大池大小。

  • public void setKeepAliveTime(long time, TimeUnit unit) —— 设置持续存活时间 —— 设置当前调度线程池执行器的持续存活时间。

  • public void setThreadFactory(ThreadFactory threadFactory) —— 设置线程工厂 —— 设置当前调度线程池执行器的线程工厂。

  • public void setRejectedExecutionHandler(RejectedExecutionHandler handler) —— 设置拒绝执行处理器 —— 设置当前调度线程池执行器的拒绝执行处理器。

  • public void setContinueExistingPeriodicTasksAfterShutdownPolicy(boolean value) —— 设置关闭后继续存在周期任务集策略 —— 设置当前调度线程池执行器被关闭后是否允许继续执行旧周期任务。

  • public void setExecuteExistingDelayedTasksAfterShutdownPolicy(boolean value) —— 设置关闭后继续存在延迟任务集策略 —— 设置当前调度线程池执行器被关闭后是否允许继续执行旧延迟任务。

  • public void setRemoveOnCancelPolicy(boolean value) —— 设置取消移除策略 —— 设置当前调度线程池执行器中的任务是否需要在取消时从工作队列中移除。
     

 检查

  • public int getCorePoolSize() —— 获取核心池大小 —— 获取当前调度线程池执行器的核心池大小。

  • public int getMaximumPoolSize() —— 获取最大池大小 —— 获取当前调度线程池执行器的最大池大小。

  • public long getKeepAliveTime(TimeUnit unit) —— 获取持续存活时间 —— 获取当前调度线程池执行器的持续存活时间。

  • public BlockingQueue getQueue() —— 获取队列 —— 获取当前调度线程池执行器的工作队列。

  • public ThreadFactory getThreadFactory() —— 获取线程工厂 —— 获取当前调度线程池执行器的线程工厂。

  • public RejectedExecutionHandler getRejectedExecutionHandler() —— 获取拒绝执行处理器 —— 获取当前调度线程池执行器的拒绝执行处理器。

  • public int getPoolSize() —— 获取池大小 —— 获取当前调度线程池执行器的核心执行线程总数。

  • public int getActiveCount() —— 获取活跃总数 —— 获取当前调度线程池执行器正在执行任务的执行线程总数的近似值。

  • public int getLargestPoolSize() —— 获取最大池大小 —— 获取当前调度线程池执行器的最多并存的执行线程总数。

  • public long getTaskCount() —— 获取任务总数 —— 获取当前调度线程池执行器中旧任务总数的近似值。

  • public long getCompletedTaskCount() —— 获取完成任务总数 —— 获取当前调度线程池执行器中结束任务总数的近似值。

  • public boolean getContinueExistingPeriodicTasksAfterShutdownPolicy() —— 获取关闭后继续存在周期任务集策略 —— 判断当前调度线程池执行器被关闭后是否允许继续执行旧周期任务。

  • public boolean getContinueExistingPeriodicTasksAfterShutdownPolicy() —— 获取关闭后继续存在延迟任务集策略 —— 判断当前调度线程池执行器被关闭后是否允许继续执行旧延迟任务。

  • public boolean getRemoveOnCancelPolicy() —— 获取取消移除策略 —— 判断当前调度线程池执行器中的任务是否需要在取消时从工作队列中移除。
     

 预启动

  • public boolean prestartCoreThread() —— 预启动核心线程 —— 为当前调度线程池执行器创建一个核心执行线程,成功则返回true;否则返回false。

  • public int prestartAllCoreThreads() —— 预启动所有核心线程集 —— 为当前调度线程池执行器创建最大数量的核心执行线程,并返回成功创建的核心执行线程数量。
     

 清理

  • public boolean remove(Runnable task) —— 移除 —— 从当前调度线程池执行器的工作队列中移除指定可运行任务,成功则返回true;否则返回false。

  • public void purge() —— 清洗 —— 从当前调度线程池执行器的工作队列中移除所有已取消的可运行任务。
     

 关闭

  • *public void shutdown() —— 关闭 —— 关闭当前调度线程池执行器以令其终止。关闭后的当前调度线程池执行器会拒绝新任务的递交,但会允许旧(等待中/执行中)任务的执行。该方法不会等待所有旧任务执行结束(完成/异常/取消)后返回。

  • public List shutdownNow() —— 立即关闭/停止 —— 停止当前调度线程池执行器以令其终止。停止后的当前调度线程池执行器会拒绝新任务的递交,并会取消(阻止/中断)旧任务的执行,最后返回等待中的可运行任务集。方法不会等待所有旧任务执行结束后返回。

  • public boolean isShutdown() —— 是否关闭/停止 —— 判断当前调度线程池执行器是否关闭/停止,是则返回true;否则返回false。
     

 终止

    终止是调度线程池执行器关闭/停止的最终状态。已终止的调度线程池执行器会拒绝新任务的递交,并且旧任务已全部执行结束,还会销毁内部所有的执行线程并永远不再创建。此时的调度线程池执行器已经彻底失去任务的调度执行能力,该知识点会在下文讲解关闭/停止/终止时详述。

  • public boolean isTerminating() —— 是否终止中 —— 判断当前调度线程池执行器是否正在终止,是则返回true;否则返回false。由于关闭/停止是终止的前提,因此该方法只有在shutdown()/shutdownNow()方法调用后调用才有可能返回true。

  • public boolean isTerminated() —— 是否终止 —— 判断当前调度线程池执行器是否终止,是则返回true;否则返回false。由于关闭/停止是终止的前提,因此该方法只有在shutdown()/shutdownNow()方法调用后调用才有可能返回true。

  • public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException —— 等待终止 —— 等待当前调度线程池执行器终止。当前调度线程池执行器会有限等待至终止并返回true;超出指定等待时间则返回false。
     

 钩子

  • protected void beforeExecute(Thread t, Runnable r) —— 执行之前 —— 重写该方法可自定义当前调度线程池执行器执行任务前的前置操作,但该方法执行异常也将导致任务无法执行。

  • protected void afterExecute(Runnable r, Throwable t) —— 执行之后 —— 重写该方法可自定义当前调度线程池执行器执行任务后的后置操作,这其中异常参数被固定用于接收execute(Runnable command)方法执行失败时抛出的异常。

  • protected void terminated() —— 终止 —— 重写该方法可自定义当前调度线程池执行器终止时的操作。

  • void onShutdown() —— 在关闭 —— 重写该方法可自定义当前调度线程池执行器关闭/停止时的操作。
     
     

实现


    调度线程池执行器类是线程池执行器类的子类,直接继承了其对任务的执行能力。因此本文只会重点讲解调度线程池执行器类的新增特性而不会对旧有内容进行详述,对该部分内容有需求的同学可通过本文头部的相关链接跳转至线程池执行器类的专项文章。
 

 延迟工作队列/可运行调度未来/调度未来任务/装饰

    调度线程池执行器类对任务的延迟执行能力依赖DelayedWorkQueue @ 延迟工作队列类实现。与线程池执行器的多样性选择不同,调度线程池执行器会固定使用延迟工作队列作为[workQueue @ 工作队列],原因是其需要借助延迟工作队列类的元素延迟移除能力来实现调度的延迟执行功能。由此我们可知调度线程池执行器类并不具备令任务“直接”延迟执行的能力,其只是保证了自身能够获取到的任务必然已经延迟到期。而为了绝对性的保证这一点,任务递交后会被加入[工作队列]以维护获取渠道的唯一性,也因此调度线程池执行器的执行线程不会随任务同步创建,而是在任务成功入队后调用ensurePrestart()方法“预”创建的…相关源码如下:

/*** Main execution method for delayed or periodic tasks.  If pool is shut down, rejects the task.* Otherwise adds task to queue and starts a thread, if necessary, to run it.  (We cannot prestart* the thread to run the task because the task (probably) shouldn't be run yet.)  If the pool is * shut down while the task is being added, cancel and remove it if required by state and * run-after-shutdown parameters.*/
private void delayedExecute(RunnableScheduledFuture<?> task) {// ---- 如果调度线程池执行器已关闭,直接拒绝任务。if (isShutdown())reject(task);else {// ---- 如果没有关闭,将任务加入延迟工作队列中。并再次判断是否关闭,以及是否禁止,是则移除并取消任务,否则// 创建一个核心线程以执行任务。ensurePrestart()方法由线程池执行器类提供,当执行线程总数已达最大核心线程时// 该方法调用没有意义。super.getQueue().add(task);if (isShutdown() && !canRunInCurrentRunState(task.isPeriodic()) && remove(task))task.cancel(false);elseensurePrestart();}
}

    延迟工作队列类是调度线程池执行器类定制的“移除”优化类。一个值得思考的问题是:调度线程池执行器类为什么不直接将DelayedQueue @ 延迟队列作为固定[工作队列]呢?毕竟其作为BlockingQueue @ 阻塞队列接口的公共实现类也同样具备元素延迟能力…而事实上这是调度线程池执行器类为了增强取消节点的移除性能而做出的设计。

    与线程池执行器不同,调度线程池执行器中的任务其实有很大概率会被取消。因为其执行条件/前提可能在正式执行前失效,由此就需要取消任务以避免执行,从而就使得[工作队列]中可能存在大量取消任务。取消任务的大量遗留会对调度线程池执行器的性能造成负面影响,而想要避免这种情况的最好方法是在取消时将任务同步移除。但遗憾的是移除本身也并不高效,原因是这些取消任务可能存在于[工作队列]的任意位置,而想要准确找到/安全移除这些取消任务就往往需要辅以全局性的遍历/加锁手段,显然这还会加剧程序运行开销/性能的负面影响,因此才会有“移除”优化类延迟工作队列的存在。延迟工作队列类是调度线程池执行器类的默认静态内部类,不允许被非调度线程池执行器类子类/同包类的外部类访问。延迟工作队列类在延迟队列类的基础上从元素类型/排序角度出发对元素定位进行了优化,使得元素内部移除的时间复杂度由原本的O(n)降低为了O(log n),并在GC方面也有一定的额外收益。而如果略去这些优化项不看,那延迟工作队列类与延迟队列类在代码实现上可以说是完全一致的…该知识点会在延迟工作队列类的专项文章中详述。

    延迟工作队列类只支持RunnableScheduledFuture @ 可运行调度未来类型的任务。可运行调度未来接口是任务功能/任务/延迟/周期性的概念封装,用于为代理任务附加调度机制所需的底层数据/方法支持,并继承了RunnableFuture(可运行未来)接口追踪/干预/获取代理任务状态/过程/结果的能力。因此当任务被递交至调度线程池执行器时,其会被第一时间封装为可运行调度任务以配合调度机制,随后才会被加入[工作队列]/延迟工作队列中保存。不过任务的封装也并没有想象中那么简单直接,因为其需要经历“基础封装”和“装饰封装”两步。

    任务会被“基础封装”为ScheduledFutureTask @ 调度未来任务。所谓“基础封装”是指将任务被封装为调度未来任务的过程,该类是任务功能/任务/延迟/周期性的实现封装,即是可运行调度未来接口的具体实现类,用于为代理任务的调度执行提供了一切基本/部分优化支持。这包含但不限于以下内容,而这些知识点都会在调度未来任务类的专项文章中详述。

  • 计算代理任务的[time @ 时间]以作为延迟工作队列实现延迟的时间依据;
  • 记录由调度未来任务类分配的唯一[sequenceNumber @ 序号]以在执行时间/延迟时间相同的情况下确保调度未来任务间的顺序正确性;
  • 在代理任务执行结束后将[outerTask @ 外部任务]加入延迟工作队列以实现周期执行;
  • 启动/追踪/干预/获取代理任务的执行流程/状态/过程/结果;
  • 在代理任务取消时将调度未来任务从延迟工作队列中同步移除;
  • 记录自身位于延迟工作队列上的[heapIndex @ 堆索引]以优化取消移除的性能。

    务可被“装饰封装”为其它可运行调度未来接口实现类的实例。碍于实际需求的原因,开发者可能会使用自定义的可运行调度未来接口实现类(这通常是调度未来任务类的子类,因为从零实现相应功能齐全的可运行调度未来接口实现类成本/难度很高,也没有这个必要)来封装任务,从而得以实现非常规的调度执行。而为了支持这种情况调度线程池执行器会在完成“基础封装”后继续对任务进行“装饰封装”,目的就是为了让开发者可以在此基础上添加自定义行为。需要注意的是这种自定义行为并没有被强制指定操作类型,因此其虽然最常用于实现任务的另类封装,但其实也可以只是简单添加某些代理措施/行为,只需重写相关的钩子方法即可…具体源码/示例如下:

/*** Modifies or replaces the task used to execute a runnable. This method can be used to override * the concrete class used for managing internal tasks. The default implementation simply returns * the given task.** @param runnable the submitted Runnable 任务* @param task     the task created to execute the runnable "基础封装"的调度未来任务* @param <V>      the type of the task's result* @return a task that can execute the runnable* @since 1.6*/
protected <V> RunnableScheduledFuture<V> decorateTask(Runnable runnable, RunnableScheduledFuture<V> task) {// ---- 默认实现是直接将调度未来任务返回。return task;// ---- 也可以只是简单的添加某些代理行为,例如打印一行日志。System.out.println("任务已成功封装为可调度未来任务!");return task;// ---- 还可以将任务封装为其它可运行调度未来接口实现类实例,这是最常用的做法,也是装饰设计的初衷。return new CustomScheduledFutureTask<Void>(command, null, triggerTime(delay, unit));// ---- 甚至可以对可运行调度未来进行再封装。return new CustomScheduledFutureTask<Void>(task, null, triggerTime(delay, unit));
}/*** Modifies or replaces the task used to execute a callable. This method can be used to override * the concrete class used for managing internal tasks. The default implementation simply returns * the given task.** @param callable the submitted Callable 任务* @param task     the task created to execute the runnable "基础封装"的调度未来任务* @param <V>      the type of the task's result* @return a task that can execute the callable* @since 1.6*/
protected <V> RunnableScheduledFuture<V> decorateTask(Callable<V> callable, RunnableScheduledFuture<V> task) {// ---- 默认实现是直接将调度未来任务返回,但也重写该方法将任务封装为其它可运行调度未来接口实现类实例。return task;
}

    关于装饰有一个值得特别讲解的点是:当任务选择周期执行时,调度线程池执行器会将“基础封装”的调度未来任务的[外部任务]赋值为“装饰封装”的可运行调度未来,即使可运行调度未来就是调度未来任务本身。而当调度未来任务成功执行代理任务后,其实际加入延迟工作队列的也只是[外部任务]而并非自身…具体源码/示例如下:

/*** @throws RejectedExecutionException {@inheritDoc}* @throws NullPointerException       {@inheritDoc}* @throws IllegalArgumentException   {@inheritDoc}*/
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;
}/*** Overrides FutureTask version so as to reset/requeue if periodic.*/
public void run() {boolean periodic = isPeriodic();// ---- 如果当前调度线程池执行器的状态已已经不支持执行任务了,直接取消任务。if (!canRunInCurrentRunState(periodic))cancel(false);// ---- 如果是非周期任务,直接调用父类FutureTask的run()方法执行任务。else if (!periodic)ScheduledFutureTask.super.run();// ---- 如果是周期任务,直接调用父类FutureTask的runAndReset()方法执行任务,该方法与run()方法的区别在于// 不会设置<完成>状态,也不会保存任务执行结果。而任务执行成功后,方法还会周期任务的新一轮执行时间,并将任务// 重新加入延迟工作队列中以实现周期/重复执行。else if (ScheduledFutureTask.super.runAndReset()) {// ---- 计算下个执行时间, 并将调度未来任务的[外部任务]重新加入延迟工作队列。正常情况下,调度未来任务// 与其[外部任务]是相同对象。setNextRunTime();reExecutePeriodic(outerTask);}
}

    [外部任务]用于保证“组合”可运行调度未来接口实现类对象的重入队正确性。上文中我们说过自定义的可运行调度未来接口实现类大都是继承调度未来任务类后的再拓展,因为从零实现相应功能齐全的可运行调度未来接口实现类成本/难度很高,也没有这个必要。但与此同时我们也知道使用继承会导致自定义的可运行调度未来接口实现类出现包含但不限于耦合度高/拓展性低/简洁性差等结构性问题,因此部分开发者也会选择使用“组合”的方式来快速创建可运行调度未来接口的实现类,即在实现类内部组合调度未来任务类型字段的来承载具体实例并调用同名方法…大致示例代码如下:

public class CustomeScheduledFutureTask<V> implements RunnableScheduledFuture<V> {// ---- 组合调度未来任务类型的字段来承载具体实例,该实例应在"基础封装"时获得,并在创建自定义对象时传入。private ScheduledFutureTask<V> scheduledFutureTask;public CustomeScheduledFutureTask(ScheduledFutureTask<V> scheduledFutureTask) {this.scheduledFutureTask = scheduledFutureTask}// 在实现RunnableScheduledFuture的方法时调用scheduledFutureTask的同名方法。public void run() {// ---- 添加前置代理行为;doPre(...);// ---- 调用同名方法,其它方法同理。scheduledFutureTask.run();// ---- 添加后置代理行为;doAfter(...);}}

    “组合”虽然解决了耦合度高/拓展性低/简洁性差等结构性问题,但同时也会对自定义可运行调度未来接口实现类对象的重入队造成影响。因为重入队行为发生于调度未来任务类重写的run()方法中,因此如果[外部任务]字段不存在,那调度未来任务能够重入队的就只有自身而并非其所属的自定义可运行调度未来接口实现类对象。显然这是不合理的,因此调度未来任务类才会设计[外部任务]来持有自定义可运行调度未来接口实现类的对象以保证其重入队的正确性。
 

 关闭/停止/终止

    调度线程池执行器类的关闭/停止/终止相关机制虽然直接继承自线程池执行器类,但其自身也在此基础上新增了两项配置以控制调度执行所带来的某些非常规影响,从而确保其在日常开发的使用中不会出现出乎意外的异常情况。这两项配置分别为[executeExistingDelayedTasksAfterShutdown @ 关闭后继续存在延迟任务集]与[continueExistingPeriodicTasksAfterShutdown @ 关闭后继续存在周期任务集]。

    [关闭后继续存在延迟任务集]用于控制调度线程池执行器关闭后能否继续执行延迟性任务。延迟性任务顾名思义是指会被延迟执行的任务,虽说调度线程池执行器类也支持任务的无延迟执行,但实际上这只是延迟到期的一种特殊体现,因此调度线程池执行器的任务理论上都属于延迟性任务。这其中甚至也包括周期性任务,因为所谓周期执行本质上也不过就是任务的多次延迟执行而已,但此处为了与周期性任务进行区分我们只将单次执行的任务称为延迟性任务。我们应该已经知道的是:调度线程池执行器会在关闭后拒绝新任务的递交,并在所有旧任务执行结束后终止。但延迟性任务的存在可能会对该场景造成影响,因为任务的延迟时间可能会超乎意料的长,从而导致调度线程池执行器的终止也被无限拖后,因此调度线程池执行器类会通过[关闭后继续存在延迟任务集]来控制延迟性任务能否在关闭后继续执行。该字段的值可通过相应的set方法动态设置,但其初始值却被固定为true,因为超长的延迟时间即使真的存在也终究只是小概率事件,因此令调度线程池执行器在执行完所有的延迟性任务后再终止通常是可以被接受的。

    [关闭后继续存在周期任务集]用于控制调度线程池执行器关闭后能否继续执行周期性任务。周期性任务的存在同样会对“关闭执行”场景造成破坏,因为周期性任务理论上可以无限执行,因此如果不对该情况进行精细化控制,则调度线程池执行器可能就永远都无法如预想中那般被终止,因此调度线程池执行器类会通过[关闭后继续存在周期任务集]来控制周期性任务能否在关闭后继续执行。该字段的值同样可通过相应的set方法动态设置,并且其初始值被固定为false,目的是防止开发者在不了解该机制的情况下错误使用而无法终止调度线程池执行器。

    [关闭后继续存在延迟任务集]/[关闭后继续存在周期任务集]会从存储/执行两个方面控制任务的调度执行…其具体行为/源码如下所示:

  • 延迟/周期性任务在首次加入延迟工作队列后如果发现调度线程池执行器已“关闭”且“延迟/周期禁止”则会被取消并移除;
  • 延迟/周期性任务在周期加入延迟工队队列前如果发现调度线程池执行器已“关闭”且“延迟/周期禁止”则会被直接抛弃;
  • 延迟/周期性任务在周期加入延迟工队队列后如果发现调度线程池执行器已“关闭”且“延迟/周期禁止”则会被取消并移除;
  • 延迟/周期性任务在从延迟工作队列中移除后如果发现调度线程池执行器已“关闭”且“延迟/周期禁止”则会被取消;
  • 调度线程池执行器“关闭”后如果发现自身已“延迟/周期禁止”则会遍历延迟工作队列以取消/移除所有延迟/周期性任务;
  • 调度线程池执行器“延迟/周期禁止”后如果发现自身已“关闭”则会遍历延迟工作队列以取消/移除所有延迟/周期性任务;
/*** Cancels and clears the queue of all tasks that should not be run due to shutdown policy.  * Invoked within super.shutdown.** onShutdown()方法是由线程池执行器类提供钩子方法,用于在关闭时执行自定义逻辑。调度线程池执行器类*/
@Override
void onShutdown() {// ---- 获取[工作队列]/[关闭后执行存在延迟任务集]/[关闭后继续存在周期任务集]。BlockingQueue<Runnable> q = super.getQueue();boolean keepDelayed = getExecuteExistingDelayedTasksAfterShutdownPolicy();boolean keepPeriodic = getContinueExistingPeriodicTasksAfterShutdownPolicy();if (!keepDelayed && !keepPeriodic) {// ---- 如果[关闭后执行存在延迟任务集]/[关闭后继续存在周期任务集]都为false,则遍历[工作队列]以取消// 所有的任务,并最后一次性清理。for (Object e : q.toArray())if (e instanceof RunnableScheduledFuture<?>)((RunnableScheduledFuture<?>) e).cancel(false);q.clear();} else {// Traverse snapshot to avoid iterator exceptions// 遍历快照以避免迭代器异常// ---- 如果[关闭后执行存在延迟任务集]/[关闭后继续存在周期任务集]不都为false,则遍历[工作队列]根据// 任务是否周期性对应相关的字段以取消/移除。而这期间如果遭遇了取消节点则也会触发移除。for (Object e : q.toArray()) {if (e instanceof RunnableScheduledFuture) {RunnableScheduledFuture<?> t = (RunnableScheduledFuture<?>) e;if ((t.isPeriodic() ? !keepPeriodic : !keepDelayed) || t.isCancelled()) {// also remove if already cancelledif (q.remove(t))t.cancel(false);}}}}// ---- 当任务的取消/清理完成后,尝试终止调度线程池执行器。tryTerminate();
}

 

 取消/清理

    调度线程池执行器类通过[removeOnCancel @ 取消移除]控制是否同步移除已取消的调度未来任务。在上文中我们说过延迟/周期性任务的取消概率其实非常之大,因为其执行条件/前提可能在正式执行前失效,因此在延迟工作队列中可能会存在大量取消任务。调度线程池执行器类虽然针对这些取消任务的同步移除设计有“延迟工作队列类/调度未来任务类”等专项优化类,但实际上其其实并不推荐在任务取消时将之同步移除。因为优化措施大多也只能弥补性能而难以令移除损耗小于运行损耗,即难以令移除造成的性能影响小于遗留造成的性能影响。故而如果没有特殊需求,调度线程池执行器类其实更推荐将取消节点保留在延迟工作队列中,并最终在正式执行时自然过滤。因此调度线程池执行器类会通过初始值为false的[取消移除]来控制是否同步移除已取消的调度未来任务,并通过相应的set方法动态赋值。为什么只控制调度未来任务的同步移除呢?这是因为[取消移除]虽然是调度线程池执行器类的字段,但基于该字段控制移除及移除本身都是调度未来任务类的自我实现。因此如果开发者是完全自主创建的可运行调度未来接口实现类,那就需要在cancel(boolean mayInterruptIfRunning)方法中嵌入相关逻辑,否则[取消移除]及其赋值行为是没有任何意义的…相关代码如下所示:

// 调度未来任务类的cancel(boolean mayInterruptIfRunning)方法实现。
public boolean cancel(boolean mayInterruptIfRunning) {// ---- 方法首先会调用线程池执行器类的cancel(boolean mayInterruptIfRunning)方法取消当前调度未来任务的// 代理任务,随后在取消成功/[取消后移除]为true/[堆索引] >= 0的情况下将当前调度未来任务从[工作队列]中允许/安// 全的移除。最后返回取消的结果。boolean cancelled = super.cancel(mayInterruptIfRunning);if (cancelled && removeOnCancel && heapIndex >= 0)remove(this);return cancelled;
}

    [取消移除]只对为true期间的调度未来任务取消生效。这句话的意思是[取消移除]并不具备全局性,即调度未来任务的取消虽然会在[取消移除]为true时将自身同步移除,但[取消移除]为true却并不会全局清理延迟工作队中的已取消调度未来任务,因此那些在[取消移除]为false期间取消的调度未来任务会依然保存在延迟工作队列中,这进一步体现了调度线程池执行器类其实使不推荐将任务在取消时同步移除的。

    调度线程执行器类会在[取消移除]的控制基础上强制移除取消任务。与开发者可能因为各种原因取消任务一样,调度未来执行器类也会出于令程序顺利运行的目的取消任务,并且还会在[取消移除]的基础上将取消任务从延迟工作队列中强制移除。这其中最典型的案例便是[关闭后继续存在延迟任务集]/[关闭后继续存在周期任务集]。因为在调度线程池执行器已“关闭”且“延迟/周期禁止”的情况下,延迟/周期性任务会被取消并从延迟工作队列中移除。该移除指的并非是取消中嵌入的同步移除,而是调度线程池执行器在此基础上附加的额外行为。这里可能会令人产生疑惑的是:既然调度线程池执行器不推荐同步移除取消任务,那此处又为何要不顾[取消移除]而强制移除取消任务呢?实际上主要是为了加速已关闭调度线程池执行器的终止,因为在有执行线程存活的情况下调度线程池执行器是无法被彻底终止的,而延迟工作队列中没有可获取的任务又是执行线程销毁的前提,因此调度线程池执行器才会强制移除取消任务以加速这一点。


http://www.mrgr.cn/news/62827.html

相关文章:

  • 如何在短时间内入门并掌握深度学习?
  • [网络基础]--VLAN虚拟局域网的理论讲解和配置(三种链路类型)
  • [ 应急响应靶场实战 ] VMware 搭建win server 2012应急响应靶机 攻击者获取服务器权限上传恶意病毒 防守方人员应急响应并溯源
  • 17. 从尾到头打印链表
  • ‘’‘’笔记
  • 《神经网络助力战场车辆及部件损毁识别与评估》
  • DBeaver如何查看ER图
  • Python定义与调用函数
  • 【AI时代】普通程序员想投身AI大模型行业,该如何快速入局
  • DAY67WEB 攻防-Java 安全JNDIRMILDAP五大不安全组件RCE 执行不出网
  • 服务器宝塔安装哪吒监控
  • 数据结构(8.5_1)——归并排序
  • 通过QAxObject关闭已经打开的指定名称的Word文档
  • 【安装配置教程】一、windows安装并配置java8
  • RabbitMQ怎么保障消息的可靠性
  • aab上架谷歌市场流程(apk)
  • python爬取旅游攻略(1)
  • 强化学习数学基础学习(三)
  • 【随笔】为什么transformer的FFN先升维后降维FFN的作用
  • 搜维尔科技:Manus数据手套在水下捕捉精确的手指动作, 可以在有水的条件下使用
  • 全面解析云渲染:定义、优势、分类与发展历程
  • java-参数传递与接收
  • 基于SSM+小程序的宿舍管理系统(宿舍1)
  • 【VM实战】VMware迁移到VirtualBox
  • 【c++篇】:模拟实现string类--探索字符串操作的底层逻辑
  • vite构建Vue3项目:封装公共组件,发布npm包,自定义组件库