摘要:今天我们就来一起手撕ScheduledThreadPoolExecutor类的源代码。

本文分享自华为云社区《深度解析ScheduledThreadPoolExecutor类的源代码》,作者:冰 河。

在之前的文章中,我们深度分析了ThreadPoolExecutor类的源代码,而ScheduledThreadPoolExecutor类是ThreadPoolExecutor类的子类。今天我们就来一起手撕ScheduledThreadPoolExecutor类的源代码。

构造方法

我们先来看下ScheduledThreadPoolExecutor的构造方法,源代码如下所示。

  1. public ScheduledThreadPoolExecutor(int corePoolSize) {
  2. super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue());
  3. }
  4. public ScheduledThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory) {
  5. super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
  6. new DelayedWorkQueue(), threadFactory);
  7. }
  8. public ScheduledThreadPoolExecutor(int corePoolSize, RejectedExecutionHandler handler) {
  9. super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
  10. new DelayedWorkQueue(), handler);
  11. }
  12. public ScheduledThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
  13. super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
  14. new DelayedWorkQueue(), threadFactory, handler);
  15. }

从代码结构上来看,ScheduledThreadPoolExecutor类是ThreadPoolExecutor类的子类,ScheduledThreadPoolExecutor类的构造方法实际上调用的是ThreadPoolExecutor类的构造方法。

schedule方法

接下来,我们看一下ScheduledThreadPoolExecutor类的schedule方法,源代码如下所示。

  1. public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
  2. //如果传递的Runnable对象和TimeUnit时间单位为空
  3. //抛出空指针异常
  4. if (command == null || unit == null)
  5. throw new NullPointerException();
  6. //封装任务对象,在decorateTask方法中直接返回ScheduledFutureTask对象
  7. RunnableScheduledFuture<?> t = decorateTask(command, new ScheduledFutureTask<Void>(command, null, triggerTime(delay, unit)));
  8. //执行延时任务
  9. delayedExecute(t);
  10. //返回任务
  11. return t;
  12. }
  13. public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit)
  14. //如果传递的Callable对象和TimeUnit时间单位为空
  15. //抛出空指针异常
  16. if (callable == null || unit == null)
  17. throw new NullPointerException();
  18. //封装任务对象,在decorateTask方法中直接返回ScheduledFutureTask对象
  19. RunnableScheduledFuture<V> t = decorateTask(callable,
  20. new ScheduledFutureTask<V>(callable, triggerTime(delay, unit)));
  21. //执行延时任务
  22. delayedExecute(t);
  23. //返回任务
  24. return t;
  25. }

从源代码可以看出,ScheduledThreadPoolExecutor类提供了两个重载的schedule方法,两个schedule方法的第一个参数不同。可以传递Runnable接口对象,也可以传递Callable接口对象。在方法内部,会将Runnable接口对象和Callable接口对象封装成RunnableScheduledFuture对象,本质上就是封装成ScheduledFutureTask对象。并通过delayedExecute方法来执行延时任务。

在源代码中,我们看到两个schedule都调用了decorateTask方法,接下来,我们就看看decorateTask方法。

decorateTask方法

decorateTask方法源代码如下所示。

  1. protected <V> RunnableScheduledFuture<V> decorateTask(Runnable runnable, RunnableScheduledFuture<V> task) {
  2. return task;
  3. }
  4. protected <V> RunnableScheduledFuture<V> decorateTask(Callable<V> callable, RunnableScheduledFuture<V> task) {
  5. return task;
  6. }

通过源码可以看出decorateTask方法的实现比较简单,接收一个Runnable接口对象或者Callable接口对象和封装的RunnableScheduledFuture任务,两个方法都是将RunnableScheduledFuture任务直接返回。在ScheduledThreadPoolExecutor类的子类中可以重写这两个方法。

接下来,我们继续看下scheduleAtFixedRate方法。

scheduleAtFixedRate方法

scheduleAtFixedRate方法源代码如下所示。

  1. public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
  2. //传入的Runnable对象和TimeUnit为空,则抛出空指针异常
  3. if (command == null || unit == null)
  4. throw new NullPointerException();
  5. //如果执行周期period传入的数值小于或者等于0
  6. //抛出非法参数异常
  7. if (period <= 0)
  8. throw new IllegalArgumentException();
  9. //将Runnable对象封装成ScheduledFutureTask任务,
  10. //并设置执行周期
  11. ScheduledFutureTask<Void> sft =
  12. new ScheduledFutureTask<Void>(command, null, triggerTime(initialDelay, unit), unit.toNanos(period));
  13. //调用decorateTask方法,本质上还是直接返回ScheduledFutureTask对象
  14. RunnableScheduledFuture<Void> t = decorateTask(command, sft);
  15. //设置执行的任务
  16. sft.outerTask = t;
  17. //执行延时任务
  18. delayedExecute(t);
  19. //返回执行的任务
  20. return t;
  21. }

通过源码可以看出,scheduleAtFixedRate方法将传递的Runnable对象封装成ScheduledFutureTask任务对象,并设置了执行周期,下一次的执行时间相对于上一次的执行时间来说,加上了period时长,时长的具体单位由TimeUnit决定。采用固定的频率来执行定时任务。

ScheduledThreadPoolExecutor类中另一个定时调度任务的方法是scheduleWithFixedDelay方法,接下来,我们就一起看看scheduleWithFixedDelay方法。

scheduleWithFixedDelay方法

scheduleWithFixedDelay方法的源代码如下所示。

  1. public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) {
  2. //传入的Runnable对象和TimeUnit为空,则抛出空指针异常
  3. if (command == null || unit == null)
  4. throw new NullPointerException();
  5. //任务延时时长小于或者等于0,则抛出非法参数异常
  6. if (delay <= 0)
  7. throw new IllegalArgumentException();
  8. //将Runnable对象封装成ScheduledFutureTask任务
  9. //并设置固定的执行周期来执行任务
  10. ScheduledFutureTask<Void> sft =
  11. new ScheduledFutureTask<Void>(command, null,triggerTime(initialDelay, unit), unit.toNanos(-delay));
  12. //调用decorateTask方法,本质上直接返回ScheduledFutureTask任务
  13. RunnableScheduledFuture<Void> t = decorateTask(command, sft);
  14. //设置执行的任务
  15. sft.outerTask = t;
  16. //执行延时任务
  17. delayedExecute(t);
  18. //返回任务
  19. return t;
  20. }

从scheduleWithFixedDelay方法的源代码,我们可以看出在将Runnable对象封装成ScheduledFutureTask时,设置了执行周期,但是此时设置的执行周期与scheduleAtFixedRate方法设置的执行周期不同。此时设置的执行周期规则为:下一次任务执行的时间是上一次任务完成的时间加上delay时长,时长单位由TimeUnit决定。也就是说,具体的执行时间不是固定的,但是执行的周期是固定的,整体采用的是相对固定的延迟来执行定时任务。

如果大家细心的话,会发现在scheduleWithFixedDelay方法中设置执行周期时,传递的delay值为负数,如下所示。

  1. ScheduledFutureTask<Void> sft =
  2. new ScheduledFutureTask<Void>(command, null, triggerTime(initialDelay, unit), unit.toNanos(-delay));

这里的负数表示的是相对固定的延迟。

在ScheduledFutureTask类中,存在一个setNextRunTime方法,这个方法会在run方法执行完任务后调用,这个方法更能体现scheduleAtFixedRate方法和scheduleWithFixedDelay方法的不同,setNextRunTime方法的源码如下所示。

  1. private void setNextRunTime() {
  2. //距离下次执行任务的时长
  3. long p = period;
  4. //固定频率执行,
  5. //上次执行任务的时间
  6. //加上任务的执行周期
  7. if (p > 0)
  8. time += p;
  9. //相对固定的延迟
  10. //使用的是系统当前时间
  11. //加上任务的执行周期
  12. else
  13. time = triggerTime(-p);
  14. }

在setNextRunTime方法中通过对下次执行任务的时长进行判断来确定是固定频率执行还是相对固定的延迟。

triggerTime方法

在ScheduledThreadPoolExecutor类中提供了两个triggerTime方法,用于获取下一次执行任务的具体时间。triggerTime方法的源码如下所示。

  1. private long triggerTime(long delay, TimeUnit unit) {
  2. return triggerTime(unit.toNanos((delay < 0) ? 0 : delay));
  3. }
  4. long triggerTime(long delay) {
  5. return now() +
  6. ((delay < (Long.MAX_VALUE >> 1)) ? delay : overflowFree(delay));
  7. }

这两个triggerTime方法的代码比较简单,就是获取下一次执行任务的具体时间。有一点需要注意的是:delay < (Long.MAX_VALUE >> 1判断delay的值是否小于Long.MAX_VALUE的一半,如果小于Long.MAX_VALUE值的一半,则直接返回delay,否则需要处理溢出的情况。

我们看到在triggerTime方法中处理防止溢出的逻辑使用了overflowFree方法,接下来,我们就看看overflowFree方法的实现。

overflowFree方法

overflowFree方法的源代码如下所示。

  1. private long overflowFree(long delay) {
  2. //获取队列中的节点
  3. Delayed head = (Delayed) super.getQueue().peek();
  4. //获取的节点不为空,则进行后续处理
  5. if (head != null) {
  6. //从队列节点中获取延迟时间
  7. long headDelay = head.getDelay(NANOSECONDS);
  8. //如果从队列中获取的延迟时间小于0,并且传递的delay
  9. //值减去从队列节点中获取延迟时间小于0
  10. if (headDelay < 0 && (delay - headDelay < 0))
  11. //将delay的值设置为Long.MAX_VALUE + headDelay
  12. delay = Long.MAX_VALUE + headDelay;
  13. }
  14. //返回延迟时间
  15. return delay;
  16. }

通过对overflowFree方法的源码分析,可以看出overflowFree方法本质上就是为了限制队列中的所有节点的延迟时间在Long.MAX_VALUE值之内,防止在ScheduledFutureTask类中的compareTo方法中溢出。

ScheduledFutureTask类中的compareTo方法的源码如下所示。

  1. public int compareTo(Delayed other) {
  2. if (other == this) // compare zero if same object
  3. return 0;
  4. if (other instanceof ScheduledFutureTask) {
  5. ScheduledFutureTask<?> x = (ScheduledFutureTask<?>)other;
  6. long diff = time - x.time;
  7. if (diff < 0)
  8. return -1;
  9. else if (diff > 0)
  10. return 1;
  11. else if (sequenceNumber < x.sequenceNumber)
  12. return -1;
  13. else
  14. return 1;
  15. }
  16. long diff = getDelay(NANOSECONDS) - other.getDelay(NANOSECONDS);
  17. return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;
  18. }

compareTo方法的主要作用就是对各延迟任务进行排序,距离下次执行时间靠前的任务就排在前面。

delayedExecute方法

delayedExecute方法是ScheduledThreadPoolExecutor类中延迟执行任务的方法,源代码如下所示。

  1. private void delayedExecute(RunnableScheduledFuture<?> task) {
  2. //如果当前线程池已经关闭
  3. //则执行线程池的拒绝策略
  4. if (isShutdown())
  5. reject(task);
  6. //线程池没有关闭
  7. else {
  8. //将任务添加到阻塞队列中
  9. super.getQueue().add(task);
  10. //如果当前线程池是SHUTDOWN状态
  11. //并且当前线程池状态下不能执行任务
  12. //并且成功从阻塞队列中移除任务
  13. if (isShutdown() &&
  14. !canRunInCurrentRunState(task.isPeriodic()) &&
  15. remove(task))
  16. //取消任务的执行,但不会中断执行中的任务
  17. task.cancel(false);
  18. else
  19. //调用ThreadPoolExecutor类中的ensurePrestart()方法
  20. ensurePrestart();
  21. }
  22. }

可以看到在delayedExecute方法内部调用了canRunInCurrentRunState方法,canRunInCurrentRunState方法的源码实现如下所示。

  1. boolean canRunInCurrentRunState(boolean periodic) {
  2. return isRunningOrShutdown(periodic ? continueExistingPeriodicTasksAfterShutdown : executeExistingDelayedTasksAfterShutdown);
  3. }

可以看到canRunInCurrentRunState方法的逻辑比较简单,就是判断线程池当前状态下能够执行任务。

另外,在delayedExecute方法内部还调用了ThreadPoolExecutor类中的ensurePrestart()方法,接下来,我们看下ThreadPoolExecutor类中的ensurePrestart()方法的实现,如下所示。

  1. void ensurePrestart() {
  2. int wc = workerCountOf(ctl.get());
  3. if (wc < corePoolSize)
  4. addWorker(null, true);
  5. else if (wc == 0)
  6. addWorker(null, false);
  7. }

在ThreadPoolExecutor类中的ensurePrestart()方法中,首先获取当前线程池中线程的数量,如果线程数量小于corePoolSize则调用addWorker方法传递null和true,如果线程数量为0,则调用addWorker方法传递null和false。

关于addWork()方法的源码解析,大家可以参考【高并发专题】中的《高并发之——通过ThreadPoolExecutor类的源码深度解析线程池执行任务的核心流程》一文,这里,不再赘述。

reExecutePeriodic方法

reExecutePeriodic方法的源代码如下所示。

  1. void reExecutePeriodic(RunnableScheduledFuture<?> task) {
  2. //线程池当前状态下能够执行任务
  3. if (canRunInCurrentRunState(true)) {
  4. //将任务放入队列
  5. super.getQueue().add(task);
  6. //线程池当前状态下不能执行任务,并且成功移除任务
  7. if (!canRunInCurrentRunState(true) && remove(task))
  8. //取消任务
  9. task.cancel(false);
  10. else
  11. //调用ThreadPoolExecutor类的ensurePrestart()方法
  12. ensurePrestart();
  13. }
  14. }

总体来说reExecutePeriodic方法的逻辑比较简单,但是,这里需要注意和delayedExecute方法的不同点:调用reExecutePeriodic方法的时候已经执行过一次任务,所以,并不会触发线程池的拒绝策略;传入reExecutePeriodic方法的任务一定是周期性的任务。

onShutdown方法

onShutdown方法是ThreadPoolExecutor类中的钩子函数,它是在ThreadPoolExecutor类中的shutdown方法中调用的,而在ThreadPoolExecutor类中的onShutdown方法是一个空方法,如下所示。

  1. void onShutdown() {
  2. }

ThreadPoolExecutor类中的onShutdown方法交由子类实现,所以ScheduledThreadPoolExecutor类覆写了onShutdown方法,实现了具体的逻辑,ScheduledThreadPoolExecutor类中的onShutdown方法的源码实现如下所示。

  1. @Override
  2. void onShutdown() {
  3. //获取队列
  4. BlockingQueue<Runnable> q = super.getQueue();
  5. //在线程池已经调用shutdown方法后,是否继续执行现有延迟任务
  6. boolean keepDelayed = getExecuteExistingDelayedTasksAfterShutdownPolicy();
  7. //在线程池已经调用shutdown方法后,是否继续执行现有定时任务
  8. boolean keepPeriodic = getContinueExistingPeriodicTasksAfterShutdownPolicy();
  9. //在线程池已经调用shutdown方法后,不继续执行现有延迟任务和定时任务
  10. if (!keepDelayed && !keepPeriodic) {
  11. //遍历队列中的所有任务
  12. for (Object e : q.toArray())
  13. //取消任务的执行
  14. if (e instanceof RunnableScheduledFuture<?>)
  15. ((RunnableScheduledFuture<?>) e).cancel(false);
  16. //清空队列
  17. q.clear();
  18. }
  19. //在线程池已经调用shutdown方法后,继续执行现有延迟任务和定时任务
  20. else {
  21. //遍历队列中的所有任务
  22. for (Object e : q.toArray()) {
  23. //当前任务是RunnableScheduledFuture类型
  24. if (e instanceof RunnableScheduledFuture) {
  25. //将任务强转为RunnableScheduledFuture类型
  26. RunnableScheduledFuture<?> t = (RunnableScheduledFuture<?>)e;
  27. //在线程池调用shutdown方法后不继续的延迟任务或周期任务
  28. //则从队列中删除并取消任务
  29. if ((t.isPeriodic() ? !keepPeriodic : !keepDelayed) ||
  30. t.isCancelled()) {
  31. if (q.remove(t))
  32. t.cancel(false);
  33. }
  34. }
  35. }
  36. }
  37. //最终调用tryTerminate()方法
  38. tryTerminate();
  39. }

ScheduledThreadPoolExecutor类中的onShutdown方法的主要逻辑就是先判断线程池调用shutdown方法后,是否继续执行现有的延迟任务和定时任务,如果不再执行,则取消任务并清空队列;如果继续执行,将队列中的任务强转为RunnableScheduledFuture对象之后,从队列中删除并取消任务。大家需要好好理解这两种处理方式。最后调用ThreadPoolExecutor类的tryTerminate方法。

点击关注,第一时间了解华为云新鲜技术~

深度解析9种ScheduledThreadPoolExecutor的构造方法的更多相关文章

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

    在上一篇线程池的文章<并发编程(十一)—— Java 线程池 实现原理与源码深度解析(一)>中从ThreadPoolExecutor源码分析了其运行机制.限于篇幅,留下了Scheduled ...

  2. 【高并发】深度解析ScheduledThreadPoolExecutor类的源代码

    在[高并发专题]的专栏中,我们深度分析了ThreadPoolExecutor类的源代码,而ScheduledThreadPoolExecutor类是ThreadPoolExecutor类的子类.今天我 ...

  3. 以两种异步模型应用案例,深度解析Future接口

    摘要:本文以实际案例的形式分析了两种异步模型,并从源码角度深度解析Future接口和FutureTask类. 本文分享自华为云社区<[精通高并发系列]两种异步模型与深度解析Future接口(一) ...

  4. 【高并发】两种异步模型与深度解析Future接口

    大家好,我是冰河~~ 本文有点长,但是满满的干货,以实际案例的形式分析了两种异步模型,并从源码角度深度解析Future接口和FutureTask类,希望大家踏下心来,打开你的IDE,跟着文章看源码,相 ...

  5. mybatis 3.x源码深度解析与最佳实践(最完整原创)

    mybatis 3.x源码深度解析与最佳实践 1 环境准备 1.1 mybatis介绍以及框架源码的学习目标 1.2 本系列源码解析的方式 1.3 环境搭建 1.4 从Hello World开始 2 ...

  6. Spring源码深度解析之事务

    Spring源码深度解析之事务 目录 一.JDBC方式下的事务使用示例 (1)创建数据表结构 (2)创建对应数据表的PO (3)创建表和实体之间的映射 (4)创建数据操作接口 (5)创建数据操作接口实 ...

  7. Deep Learning模型之:CNN卷积神经网络(一)深度解析CNN

    http://m.blog.csdn.net/blog/wu010555688/24487301 本文整理了网上几位大牛的博客,详细地讲解了CNN的基础结构与核心思想,欢迎交流. [1]Deep le ...

  8. (转载)(收藏)OceanBase深度解析

    一.OceanBase不需要高可靠服务器和高端存储 OceanBase是关系型数据库,包含内核+OceanBase云平台(OCP).与传统关系型数据库相比,最大的不同点, 是OceanBase是分布式 ...

  9. Kafka深度解析

    本文转发自Jason’s Blog,原文链接 http://www.jasongj.com/2015/01/02/Kafka深度解析 背景介绍 Kafka简介 Kafka是一种分布式的,基于发布/订阅 ...

  10. java内存分配和String类型的深度解析

    [尊重原创文章出自:http://my.oschina.net/xiaohui249/blog/170013] 摘要 从整体上介绍java内存的概念.构成以及分配机制,在此基础上深度解析java中的S ...

随机推荐

  1. 深入理解 Python 虚拟机:进程、线程和协程

    深入理解 Python 虚拟机:进程.线程和协程 在本篇文章当中深入分析在 Python 当中 进程.线程和协程的区别,这三个概念会让人非常迷惑.如果没有深入了解这三者的实现原理,只是看一些文字说明, ...

  2. YbtOJ 数位DP G.幸运666

    日常写点奇奇怪怪的乱搞做法 awa 这题跟前面几道数位 DP 的区别在于让求第 \(n\) 小的数. 虽然我不会求也不想学这个,但我们可以 binary search! 问题就转换为求 \([1,mi ...

  3. Golang面试题从浅入深高频必刷「2023版」

    大家好,我是阳哥.专注Go语言的学习经验分享和就业辅导. Go语言特点 Go语言相比C++/Java等语言是优雅且简洁的,是我最喜爱的编程语言之一,它既保留了C++的高性能,又可以像Java,Pyth ...

  4. PostgreSQL 提升子连接与 ORACLE 子查询非嵌套

    查询优化器对子查询一般采用嵌套执行的方式,即父查询中的每一行,都要执行一次子查询,这样子查询会执行很多次,效率非常低. 例如 exists.not exists 逐行取出经行匹配处理,项目中使用子查询 ...

  5. SQL改写案例1

    一开发哥们找我改写SQL,他写的逻辑始终不对,安排! -- 他写的SQL: -- order_id 是主键 with a as ( select str_to_date(regist_time,'%Y ...

  6. 【MISC】[MoeCTF 2022]cccrrc --crc32爆破

    附件下载下来为压缩包,需要密码,查看该压缩包的内容 此处发现里面四个txt文件均已被加密,但是每个txt的内容都只有四个字节,符合crc32爆破条件,直接上脚本: import binascii im ...

  7. 解决Few-shot问题的两大方法:元学习与微调

    .center { width: auto; display: table; margin-left: auto; margin-right: auto } 基于元学习(Meta-Learning)的 ...

  8. GitHub Actions 入门指南

    前言 GitHub Actions 可以构建一组自动化的工作流程,并提供了拉取请求.合并分支等事件来触发他们.一般成熟的开源项目会在每个版本发布时提供 releases ,它就是通过 Actions ...

  9. Python利用pandas进行数据合并

    当使用Python中的pandas库时,merge函数是用于合并(或连接)两个数据框(DataFrame)的重要工具.它类似于SQL中的JOIN操作,允许你根据一个或多个键(key)将两个数据框连接起 ...

  10. UVA529 加成序列

    传送门 题目分析 一道 dfs,迭代加深 我们可以很快的猜出来最终 \(m\) 的长度必然是小于 \(10\) 的. 而这种浅深度的问题正好适用于迭代加深. 之后考虑剪枝 优化搜索顺序 : 我们要让序 ...