news 2026/6/14 23:05:40

Java 线程池(第四篇):ScheduledThreadPoolExecutor 原理与定时任务执行机制全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java 线程池(第四篇):ScheduledThreadPoolExecutor 原理与定时任务执行机制全解析

在 Java 的线程池体系中,ScheduledThreadPoolExecutor是唯一一个可以执行:

  • 延迟任务(delay)
  • 周期任务(scheduleAtFixedRate / scheduleWithFixedDelay)

的线程池,也是 Timer 的完全替代品。

本篇文章我们将彻底讲透:

  • ScheduledThreadPoolExecutor 的内部结构
  • 延迟任务如何组织?
  • 周期任务和延迟任务的区别?
  • scheduleAtFixedRate 和 scheduleWithFixedDelay 的本质差异
  • 为什么 Timer 已经过时?
  • 延迟队列 DelayQueue 是如何工作的?

理解这篇,你就真正掌握 Java 定时任务的核心机制。

一、ScheduledThreadPoolExecutor 是什么?

它是 ThreadPoolExecutor 的子类,用于执行定时任务:

ScheduledExecutorService service = Executors.newScheduledThreadPool(4);

它具备三种能力:

类型方法场景
延迟任务schedule()延迟执行一次
固定速率任务scheduleAtFixedRate()间隔固定时间执行
固定延迟任务scheduleWithFixedDelay()上一次结束后,等固定延迟再执行

底层全部由一个重要结构支持:

DelayQueue(延迟队列)

二、ScheduledThreadPoolExecutor 内部结构(关键图)

它继承自 ThreadPoolExecutor,但替换了队列类型:

ThreadPoolExecutor ▲ │ ScheduledThreadPoolExecutor │ 使用 DelayedWorkQueue(一个 DelayQueue)

也就是说:

普通线程池使用 BlockingQueue
ScheduledThreadPoolExecutor 使用DelayedWorkQueue

DelayedWorkQueue 不是普通队列:

  • 任务按照“到期执行时间”排序
  • 只有到期的任务才能被线程取出执行
  • 内部使用了基于最小堆的优先队列(PriorityQueue)

三、延迟任务底层原理:基于 DelayQueue + 时间轮(类似机制)

当你执行:

service.schedule(task, 5, TimeUnit.SECONDS);

内部做了两件事:


✔ 1. 把任务包装成 ScheduledFutureTask

包含:

  • 任务本体

  • 下次执行时间(triggerTime)

  • 任务序号(用于排序)


✔ 2. 丢进 DelayedWorkQueue(DelayQueue)

DelayQueue 会:

  • 按照“执行时间”建立一个小顶堆

  • 堆顶永远是最早执行的任务

  • 线程从队列取任务时,如果没到时间,会阻塞等待

流程:

当前时间 < 任务触发时间 → 阻塞
当前时间 >= 任务触发时间 → 执行任务

这就是“延迟任务”的底层机制。

四、周期任务底层原理(重点)

Java 提供两种周期任务:


① scheduleAtFixedRate(固定速率)

scheduleAtFixedRate(task, 0, 5, SECONDS);

含义:

不管任务执行多久,每隔 5 秒触发一次。

举例:

  • 第 1 次:0s
  • 第 2 次:5s
  • 第 3 次:10s

如果一个任务执行 6 秒怎么办?

答案:

下一次任务会“补课”式触发(可能会连着执行)。

也就是说:

  • 它不关心任务是否执行完

  • 它关心的是时间点是否到了

这容易造成“任务堆积”问题。

② scheduleWithFixedDelay(固定延迟)

scheduleWithFixedDelay(task, 0, 5, SECONDS);

含义:

任务执行完后,等 5 秒再执行下一次。

举例:

  • 任务执行 6 秒

  • 等待 5 秒

  • 下一次在 11 秒执行

执行时间取决于任务执行时长。

五、两者区别(面试必问)

方法固定点执行?与任务执行时长有关?是否可能任务堆积?
scheduleAtFixedRate✔ 是❌ 否✔ 可能堆积
scheduleWithFixedDelay❌ 否✔ 是❌ 不会堆积

一句话总结:

FixedRate:按点执行(补课式)
FixedDelay:执行完再延迟(绝不堆积)

六、为什么 Timer 已经过时,必须使用 ScheduledThreadPoolExecutor?

Timer 的缺点非常致命:

❌ 1. Timer 只有一个线程,任务串行执行

❌ 2. Timer 中的一个异常会导致整个调度线程退出

❌ 3. Timer 的时间精度差,在系统时间变化时会出错

❌ 4. 不支持多线程执行任务

相比之下:

TimerScheduledThreadPoolExecutor
单线程多线程
任务阻塞会导致全部延迟任务可并行执行
异常会导致整个 Timer 停止不会导致线程池崩溃
时间精度差使用 System.nanoTime,更精确

因此:

在所有实际项目中,都必须使用 ScheduledThreadPoolExecutor 替代 Timer。

七、代码示例(延迟 + 周期任务)

① 延迟任务

ScheduledExecutorService ses = Executors.newScheduledThreadPool(2); ses.schedule(() -> { System.out.println("5 秒后执行"); }, 5, TimeUnit.SECONDS);

② 固定速率(FixedRate)

ses.scheduleAtFixedRate(() -> { System.out.println("每 3 秒触发一次,与任务执行时间无关"); }, 0, 3, TimeUnit.SECONDS);

③ 固定延迟(FixedDelay)

ses.scheduleWithFixedDelay(() -> { System.out.println("任务执行完后等 3 秒再执行,绝不堆积"); }, 0, 3, TimeUnit.SECONDS);

八、ScheduledThreadPoolExecutor 的优点总结

  • ✔ 多线程并行执行定时任务

  • ✔ 使用 DelayQueue 实现精确调度

  • ✔ 任务异常不会影响整个线程池

  • ✔ 支持延迟 + 固定速率 + 固定延迟

  • ✔ 可与 Future 结合获取执行结果

  • ✔ 比 Timer 稳定、安全、功能更强

九、小心周期任务中的“任务堆积”问题

使用scheduleAtFixedRate时:

  • 如果任务执行时间 > 周期

  • 会导致任务连续执行

例如:

scheduleAtFixedRate(task, 0, 1s) task 耗时 3s


那么时间线:

0s: task 执行(耗时 3s)
1s: 时间到了,触发第二次,但任务还没结束
3s: 第一轮结束,立即执行第二轮

这会造成堆积。

十、总结:什么时候用哪种周期任务?

场景使用方式
强调固定时间点执行,如心跳、指标采集scheduleAtFixedRate
强调任务稳定、绝不用补课scheduleWithFixedDelay
任务有可能阻塞很久scheduleWithFixedDelay
CPU 占用不可不控scheduleWithFixedDelay
系统要尽量保持节奏稳定scheduleAtFixedRate

补充:

ScheduledExecutorService 行为观察 Demo(可直接跑)

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/14 20:51:50

LobeChat vs 官方ChatGPT:谁才是更适合企业的智能对话平台?

LobeChat vs 官方ChatGPT&#xff1a;谁才是更适合企业的智能对话平台&#xff1f; 在企业智能化转型的浪潮中&#xff0c;AI对话系统早已不再是“锦上添花”的功能模块&#xff0c;而是支撑客服、运维、知识管理甚至决策辅助的核心基础设施。OpenAI 的 ChatGPT 无疑是这场变革…

作者头像 李华
网站建设 2026/6/15 9:57:48

leetcode 2110

2110: 股票平滑下跌阶段的数目示例 1 的 prices[3,2,1,4]&#xff0c;按照子数组的右端点下标分组&#xff0c;有这些连续递减子数组&#xff1a;右端点 i0&#xff1a;[3]右端点 i1&#xff1a;[3,2]&#xff0c;[2]右端点 i2&#xff1a;[3,2,1]&#xff0c;[2,1]&#xff0c…

作者头像 李华
网站建设 2026/6/15 14:19:55

关于 RocketMQ 事务消息的正确打开方式 → 你学废了吗

知识回顾 本文不讲什么是 RocketMQ &#xff0c;不讲它的实现原理&#xff0c;只想和大家探讨下它的事务消息的正确使用方式 再探讨之前&#xff0c;先带大家回顾下知识点 事务消息的设计原理 RocketMQ 在 4.3.0 版中已经支持分布式事务消息&#xff0c;采用 2PC 的思想实现事务…

作者头像 李华
网站建设 2026/6/14 21:00:52

【数据结构】建堆操作:向上调整与向下调整的数学推导与性能对比

&#x1f3e0; 个人主页: EXtreme35 &#x1f4da; 个人专栏: 专栏名称专栏主题简述《C语言》C语言基础、语法解析与实战应用《数据结构》线性表、树、图等核心数据结构详解《题解思维》算法思路、解题技巧与高效编程实践目录引言I. 堆操作的时间复杂度分析1. 堆的基本性质与…

作者头像 李华
网站建设 2026/6/14 2:27:40

运维转网安选赛道:放弃渗透测试执念,安全运维才是你的舒适区!

提到运维转行网安&#xff0c;很多人第一反应是 “学渗透测试&#xff0c;当黑客”—— 但渗透测试需要大量 “攻击技术” 的学习&#xff08;如漏洞利用、Payload 构造、社会工程学&#xff09;&#xff0c;且对编程能力、逆向思维要求较高&#xff0c;很多运维从业者学了半年…

作者头像 李华
网站建设 2026/6/14 7:18:26

39、深入探索Shell的可移植性问题与扩展特性

深入探索Shell的可移植性问题与扩展特性 1. TMOUT变量对select语句的影响 TMOUT(超时)变量会对select语句产生影响。在select循环之前,将TMOUT设置为n秒,如果在这段时间内没有输入,select语句就会退出。 2. 扩展测试工具 ksh引入了由 [[ 和 ]] 界定的扩展测试工具…

作者头像 李华