JUC下的ScheduledThreadPoolExecutor详解

news2025/1/13 15:54:16

         ScheduledThreadPoolExecutor是Java并发编程框架中一个强大且灵活的线程池实现,专为定时与周期性任务而设计。作为ThreadPoolExecutor的子类,它不仅继承了线程池管理的高效与灵活性,还内置了基于优先级队列的延迟任务调度机制,支持任务的定时执行、固定速率执行以及固定延迟执行。通过使用ScheduledThreadPoolExecutor,开发者可以方便地安排一次性或重复性的后台任务,同时得益于线程池的特性,有效复用了线程资源,减少了线程创建销毁的开销,提升了程序性能与响应速度。它广泛应用于诸如定时数据处理、定时检查与维护、定时消息推送等多种场景,是构建高可靠、高性能后台服务不可或缺的工具之一。

一、详细介绍

  ScheduledThreadPoolExecutor是Java并发包java.util.concurrent中的一个类,它是专门为定时和周期性任务执行而设计的线程池。它继承自ThreadPoolExecutor,因此具备了线程池的所有特性,同时增加了任务调度的功能。它使用一个无界优先队列来存储待执行的任务,这些任务根据它们的延时或周期进行排序。

1、类继承结构与核心组件

ScheduledThreadPoolExecutor继承自ThreadPoolExecutor,是专为定时和周期性任务设计的增强版线程池。它内部维护了一个DelayedWorkQueue(延迟工作队列),这是一个基于优先堆的无界队列,用于存储实现了Delayed接口的任务。每个入队的任务都会根据其getDelay(TimeUnit.NANOSECONDS)方法计算的延迟时间进行排序,队列顶部总是具有最早执行时间的任务。

2、核心方法
  • schedule: 安排在指定延迟后执行给定的任务一次。
  • scheduleAtFixedRate: 安排指定任务按照固定的间隔周期执行,首次执行在指定的延迟后开始,后续执行在上一次执行结束后再经过指定的周期开始。需要注意的是,如果任务执行时间超过了周期长度,下一次执行将在当前任务结束时立即开始,而不是严格按照周期间隔。
  • scheduleWithFixedDelay: 与scheduleAtFixedRate类似,但不同之处在于,如果任务执行时间超过了间隔周期,那么下一次执行将在当前任务结束后的固定延迟后开始,而不是紧接着执行。
3、内部工作原理
  1. 任务入队: 当通过上述方法安排任务时,ScheduledThreadPoolExecutor会创建一个ScheduledFutureTask,它既是Runnable也是Future,还实现了Delayed接口,允许根据执行时间排序。这个任务会被放入DelayedWorkQueue中。

  2. 任务调度: 线程池中的工作者线程会不断从DelayedWorkQueue中取出最早到期的任务进行执行。如果队列为空,则工作者线程会阻塞等待直到有任务到达其执行时间。

  3. 时间准确性: 为了提高定时任务的准确性,ScheduledThreadPoolExecutor内部使用了LockSupport.parkNanos方法进行精准的纳秒级等待,减少由于线程调度或系统负载导致的时间偏移。

4、线程池参数调整
  • corePoolSize: 核心线程数,即使没有任务执行,也会保持存活的线程数量。
  • maximumPoolSize: 线程池最大线程数,超过这个数的多余任务将被排队等待。
  • keepAliveTime: 非核心线程闲置时的超时时长,超过此时间会回收线程,仅对非核心线程有效。
  • unit: 上述参数中时间单位。
  • workQueue: 任务队列,ScheduledThreadPoolExecutor默认使用的是无界的DelayedWorkQueue
  • threadFactory: 线程工厂,用于创建新线程。
  • handler: 拒绝策略,当线程池和队列都满时,用来处理新提交的任务。
5、性能与优化
  • 任务粒度: 尽量使任务粒度适中,过细的任务会增加调度开销,过粗则可能导致资源利用率不高。
  • 资源限制: 考虑系统资源限制,合理设置线程池大小,避免资源耗尽或过度竞争。
  • 异常处理: 在任务中妥善处理异常,避免因单个任务失败导致整个线程池或工作队列受到影响。

二、使用场景 

1、定时任务
  1. 数据备份与清理: 定时自动备份数据库或清理过期日志文件,保持系统运行环境的整洁和数据的安全性。

  2. 系统维护: 如定期执行磁盘空间检查、数据库索引优化、系统健康检查等,确保系统长期稳定运行。

  3. 报告生成: 定时生成业务报表、数据分析报告,例如每日、每周或每月的销售报表,便于管理层及时了解业务状况。

2、周期性任务
  1. 心跳检测: 在分布式系统中,客户端或服务端之间的定期心跳检测,维持连接活跃,及时发现网络异常。

  2. 缓存更新: 定时刷新或更新缓存内容,如商品价格、用户信息等,确保数据的实时性和一致性。

  3. 消息推送: 定时拉取或推送消息,如新闻推送、系统通知、邮件发送等,自动化处理信息传递。

3、计划任务
  1. 资源调度: 在云计算平台中,按计划调度资源,如自动扩展或缩减云服务器实例,依据流量高峰低谷动态调整资源。

  2. 系统定时维护窗口: 在特定时间自动开启或关闭服务,如夜间进行数据库维护、系统升级等,减少对用户的影响。

  3. 定时任务调度平台: 构建企业级的任务调度中心,允许业务方提交定时任务,统一管理执行,如定时作业调度、ETL流程等。

4、实时性要求不高的定时处理
  • 数据分析与统计: 对历史数据进行定期分析,如日志分析、用户行为分析等,生成分析报告或更新数据视图。

  • 定时爬虫: 定期抓取网页内容,用于信息收集、竞争对手监控、舆情分析等,避免频繁访问导致目标网站压力过大。

5、特定行业应用
  1. 金融行业: 定时处理交易清算、账户余额更新、风险评估等,确保金融业务的准确性和安全性。

  2. 电商行业: 商品库存更新、订单状态检查、促销活动定时开启与结束等,保障购物流程顺畅。

  3. 物联网(IoT): 设备状态监测、数据收集、远程控制指令发送等,定时维护物联网设备的正常运作。

        注意:在选择使用ScheduledThreadPoolExecutor时,需综合考虑任务的性质(如是否允许任务堆积、任务执行时间是否可预测)、系统资源限制以及任务执行失败的处理策略,确保定时任务的高效、稳定执行。同时,对于任务执行频率极高或对执行时间精度要求严格的场景,可能需要额外考虑使用更为专业的定时任务调度框架或结合其他技术手段来满足需求。

三、使用示例(Java):

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ScheduledThreadPoolExecutorExample {
    public static void main(String[] args) {
        // 创建一个定长线程池,支持定时及周期性任务执行
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);

        // 延迟3秒执行一次
        scheduledThreadPool.schedule(new RunnableTask("Delayed Task"), 3, TimeUnit.SECONDS);

        // 周期性执行,首次延迟3秒,之后每2秒执行一次
        scheduledThreadPool.scheduleAtFixedRate(new RunnableTask("Fixed Rate Task"), 3, 2, TimeUnit.SECONDS);

        // 周期性执行,首次立即执行,之后每隔2秒执行一次
        scheduledThreadPool.scheduleWithFixedDelay(new RunnableTask("Fixed Delay Task"), 0, 2, TimeUnit.SECONDS);
    }

    static class RunnableTask implements Runnable {
        private final String name;

        RunnableTask(String name) {
            this.name = name;
        }

        @Override
        public void run() {
            System.out.println("Task " + name + " is running at " + System.currentTimeMillis());
        }
    }
}

 四、注意事项

1、异常处理

捕捉并妥善处理异常:如前所述,当ScheduledThreadPoolExecutor执行的任务抛出未捕获的异常时,该任务会被取消,不再继续执行。因此,务必在任务实现中使用try-catch块捕获Throwable,以防止单个任务的失败影响到整个定时任务的连续执行。可以考虑在catch块中记录日志并决定是否重新调度失败的任务。

2、线程池配置

合理配置线程池大小:核心线程数应根据任务特性和系统资源进行合理配置。过多的核心线程可能导致资源过度消耗,而过少则可能无法充分利用系统资源或导致任务排队等待时间过长。对于周期性任务,考虑任务执行时间和间隔,避免因线程不足导致任务堆积。

3、任务调度策略

理解调度策略差异scheduleAtFixedRatescheduleWithFixedDelay有显著区别。前者确保任务按照固定周期执行,即使前一次执行延迟,下一次执行也会尽快开始,可能导致任务并发执行。后者则是在上一次任务执行结束后再经过固定延迟才开始下次执行,更适用于需要确保任务间有一定间隔的情况。

4、资源释放与内存泄漏

避免资源泄露:任务执行完毕后,确保释放所有资源,包括关闭数据库连接、文件流等,防止内存泄漏。对于使用内部类或匿名类作为任务时,注意它们对外部变量的引用可能导致的潜在内存泄漏问题。

5、任务取消与终止

提供任务取消机制:利用Future接口提供的cancel方法,允许外部逻辑根据需要取消任务。同时,了解如何正确地关闭ScheduledThreadPoolExecutor,使用shutdownshutdownNow方法,并处理好后续的清理工作。

6、任务优先级与排序

注意任务排序:虽然ScheduledThreadPoolExecutor使用DelayQueue来保证任务按照延时顺序执行,但具体到相同延时的任务,执行顺序依赖于队列内部排序。若对任务有特定的优先级需求,可能需要自定义比较逻辑或使用其他调度策略。

7、监控与日志

监控与日志记录:实施有效的监控机制,跟踪任务执行状态、执行时长、线程池负载等,这对于诊断问题和性能调优至关重要。同时,详细日志记录能帮助快速定位问题。

8、系统稳定性考量

考虑系统稳定性:在设计任务时,应考虑到系统的整体稳定性,避免因单个任务长时间阻塞导致整个线程池无法处理其他任务。可以为长耗时任务设置超时处理,或者将其拆分为更小的任务单元。

五、优缺点

1、优点
  • 高效资源利用:通过重用线程池中的线程,减少了线程创建和销毁的开销,提高了系统效率和响应速度,特别是在任务执行频繁且短时的场景下效果显著。

  • 灵活的任务调度:支持多种任务调度模式,包括一次性执行、固定速率执行和固定延迟执行,满足不同应用场景的需求,如定时任务、周期性作业等。

  • 强大的扩展性:作为ThreadPoolExecutor的子类,继承了其全部功能,如自定义线程工厂、拒绝策略等,可以根据具体需求进行高度定制化配置。

  • 易于使用:通过Executors工厂类,可以快速创建一个ScheduledThreadPoolExecutor实例,降低了定时任务编程的复杂度,提高了开发效率。

  • 精确的延时与周期控制:内部使用了DelayedWorkQueue,能够精确控制任务的执行时机,尽管受制于系统调度,但相比传统的定时器类,提供了更精细的控制能力。

2、缺点
  • 资源限制与潜在的内存泄漏:作为一个无界队列,理论上可以无限添加任务,若任务产生速度远大于处理速度,可能导致内存持续增长,最终引发内存溢出。此外,未正确管理的资源(如未关闭的数据库连接)也可能引起内存泄漏。

  • 任务执行时间的影响scheduleAtFixedRate方法中,若任务执行时间超过预定周期,后续任务的执行时间将被压缩,可能导致系统负载激增。特别是在任务执行时间不稳定的情况下,难以保证任务按预期频率执行。

  • 定时不精确:尽管ScheduledThreadPoolExecutor尽可能准确地执行任务,但由于线程调度、系统负载、垃圾回收等因素,实际执行时间可能会有轻微偏移,对于时间敏感型应用可能不够理想。

  • 调试与监控困难:相较于直接管理线程的编程方式,使用线程池后,任务的执行轨迹和异常处理更加隐式,增加了问题排查的难度。需要额外的监控和日志机制来辅助调试。

  • 潜在的死锁与活锁风险:不当的任务设计或资源竞争可能导致线程死锁或活锁,尤其是在任务间存在复杂依赖关系的情况下,需要特别注意同步和并发控制。

六、可能遇到的问题及解决方案

1. 任务堆积与资源耗尽

问题描述:当提交的任务速率远高于线程池处理速率时,即使使用了无界队列DelayedWorkQueue,也可能导致任务队列无限增长,最终耗尽系统资源。

解决方案

  • 限制任务队列大小:考虑使用有界队列如ArrayBlockingQueue替代默认的无界队列,通过限制队列大小,可以避免无限制的任务积累。
  • 监控任务队列长度:实施监控机制,当队列长度达到预警阈值时,采取相应措施,如动态调整线程池大小或暂时拒绝新任务。
  • 拒绝策略调整:使用或自定义拒绝策略,如AbortPolicy直接抛出异常,CallerRunsPolicy让调用者线程执行任务,或记录日志后忽略新任务。
2. 定时不准确

问题描述:由于系统调度、垃圾回收、任务执行时间波动等因素,定时任务的实际执行时间可能与预期有偏差。

解决方案

  • 调整线程池大小:根据任务特点和系统资源,合理设置线程池大小,确保有足够的线程处理任务,减少排队等待时间。
  • 优化任务执行:缩短单个任务执行时间,减少其对后续任务的影响。对于耗时任务,考虑拆分或异步处理。
  • 使用更精确的调度器:在极端情况下,可能需要考虑使用外部专门的定时调度服务或库,以获得更精确的定时控制。
3. 异常处理不当

问题描述:任务执行中未妥善处理的异常会导致任务取消,线程终止,甚至整个线程池受到影响。

解决方案

  • 全面的异常捕获:在任务执行代码中使用try-catch包裹,确保所有异常都能被捕获并处理。
  • 记录日志与重试机制:捕获异常后记录详细日志,并根据任务性质考虑是否需要重试机制。
  • 定制化线程工厂:使用自定义线程工厂,在创建线程时设置合适的未捕获异常处理器,如Thread.setDefaultUncaughtExceptionHandler
4. 线程泄露与内存泄漏

问题描述:任务中未正确关闭资源或存在循环引用,可能导致线程无法回收,引发内存泄漏。

解决方案

  • 资源管理:确保任务执行完毕后,所有资源(如数据库连接、文件流)都被正确关闭。
  • 弱引用或软引用:对于任务中使用的大型对象,考虑使用弱引用或软引用,以降低内存泄漏风险。
  • 定期审查代码:定期进行代码审查,查找并修复潜在的内存泄漏问题。
5. 线程池管理与监控不足

问题描述:缺乏对线程池运行状态的有效监控,难以及时发现和解决问题。

解决方案

  • 实施监控:利用Java自带的管理接口(如ThreadPoolExecutorgetQueue()getActiveCount()等方法)或第三方监控工具(如Prometheus+Grafana)实施监控。
  • 报警机制:设置阈值报警,当线程池状态(如任务队列长度、线程池大小、拒绝次数)超过预设值时,发送警报通知相关人员。
  • 定期审计:定期审查线程池配置和任务执行情况,根据系统负载和性能指标进行适时调整。

        通过合理设计和使用ScheduledThreadPoolExecutor,可以有效地在Java应用中实现定时和周期性任务的高效执行。ScheduledThreadPoolExecutor是一个强大且灵活的定时任务执行框架,适合处理大量定时和周期性任务。然而,其使用时需要充分考虑资源管理、任务调度细节以及异常处理机制,以确保系统稳定高效运行。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1654106.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【stm-4】PWM驱动LED呼吸灯 PWM驱动舵机PWM驱动直流电机

1.PWM驱动LED呼吸灯 void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct); //结构体初始化输出比较单元 void TIM_OC2Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct); void TIM_OC3Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef*…

智能家居4 -- 添加接收消息的初步处理

这一模块的思路和前面的语言控制模块很相似&#xff0c;差别只是调用TCP 去控制 废话少说&#xff0c;放码过来 增添/修改代码 receive_interface.c #include <pthread.h> #include <mqueue.h> #include <string.h> #include <errno.h> #include <…

算法学习(6)-最短路径

目录 Floyd-Warshall算法 Dijkstra算法 Bellman-Ford算法 Bellman-Ford的队列优化 最短路径算法对比分析 Floyd-Warshall算法 现在回到问题&#xff1a;如何求任意两点之间的最短路径呢&#xff1f; 通过之前的学习&#xff0c; 我们知道通过深度或广度优先搜索可以求出两…

学习记录:AUTOSAR R20-11的阅读记录(五)【CP(5.11-5.19)】完

接上回&#xff1a;学习记录&#xff1a;AUTOSAR R20-11的阅读记录&#xff08;四&#xff09;【CP&#xff08;5.6-5.10&#xff09;】 五、CP 11、General&#xff08;4个&#xff09; 5.11 File Name 说明 1 AUTOSAR_EXP_ LayeredSoftwareArchitecture.pdf 描述了AUTO…

多模态大模型MLLM VIT CLIP BLIP

一、Vit模型介绍 Vit&#xff08;Vision Transformer&#xff09;即将Transformer应用于视觉领域。 Transformer输入输出都是一个序列&#xff0c;若需要应用于视觉领域&#xff0c;则需要考虑如何将一个2d图片转化为一个1d的序列&#xff0c;最直观的想法将图片中的像素点输…

merge函数占用内存过大

&#x1f3c6;本文收录于「Bug调优」专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收藏&&…

使用python将`.mat`文件转换成`.xlsx`格式的Excel文件!!

要将.mat文件转换成.xlsx格式的Excel文件 第一步&#xff1a;导入必要的库第二步&#xff1a;定义函数来转换.mat文件第三步&#xff1a;调用函数注意事项 要将.mat文件转换成.xlsx格式的Excel文件&#xff0c;并保持文件名一致&#xff0c;你可以使用scipy.io.loadmat来读取.m…

历代著名画家作品赏析-东晋顾恺之

中国历史朝代顺序为&#xff1a;夏朝、商朝、西周、东周、秦朝、西楚、西汉、新朝、玄汉、东汉、三国、曹魏、蜀汉、孙吴、西晋、东晋、十六国、南朝、刘宋、南齐、南梁、南陈、北朝、北魏、东魏、北齐、西魏、北周、隋&#xff0c;唐宋元明清&#xff0c;近代。 一、东晋著名…

92、动态规划-最小路径和

思路&#xff1a; 还是一样&#xff0c;先使用递归来接&#xff0c;无非是向右和向下&#xff0c;然后得到两种方式进行比较&#xff0c;代码如下&#xff1a; public int minPathSum(int[][] grid) {return calculate(grid, 0, 0);}private int calculate(int[][] grid, int …

全栈开发之路——前端篇(5)组件间通讯和接口等知识补充

全栈开发一条龙——前端篇 第一篇&#xff1a;框架确定、ide设置与项目创建 第二篇&#xff1a;介绍项目文件意义、组件结构与导入以及setup的引入。 第三篇&#xff1a;setup语法&#xff0c;设置响应式数据。 第四篇&#xff1a;数据绑定、计算属性和watch监视 辅助文档&…

502页 | 2024年人工智能指数报告-英文版(免费下载)

【1】关注本公众号&#xff0c;转发当前文章到微信朋友圈 【2】私信发送 【2024年人工智能指数报告】 【3】获取本方案PDF下载链接&#xff0c;直接下载即可。 如需下载本方案PPT原格式&#xff0c;请加入微信扫描以下方案驿站知识星球&#xff0c;获取上万份PPT解决方案&…

JavaScript快速入门系列-1(JavaScript简介)

第一章:JavaScript简介 1. JavaScript简介1.1 什么是JavaScript1.2 JavaScript的历史与应用1.3 环境搭建:浏览器与Node.js2. JavaScript语言基础2.1 变量声明:let, const, var2.2 数据类型:字符串、数字、布尔值、对象、数组、null与undefined2.3 运算符:算术、比较、逻辑…

html的标签

基础标签 标签描述<h1>-<h6>定义标题&#xff0c;h1最大&#xff0c;h6最小<font>定义文本的字体&#xff0c;字体尺寸&#xff0c;字体颜色<b>定义粗体文本<i>定义斜体文本<u>定义文本下划线<center>定义文本居中<p>定义段落…

Linux的虚拟机操作

一、linux系统 我们知道的系统用到的大多数是Windows系统。 Windows个人用到的有&#xff1a;win7 win10 win11 winxp 服务器用到的有&#xff1a;windows server 2003、2008、2013...........等等 linux也有系统。 centos 7 有5、6、8等等 redhat ubuntu kail 二、了…

Ansible----playbook模块之templates模块、tags模块、roles模块

目录 引言 一、templates模块 &#xff08;一&#xff09;关键信息 &#xff08;二&#xff09;实际操作 1.定义主机组 2.设置免密登录 3.分别建立访问目录 4.定义模板文件 5.创建playbook文件 6.执行剧本 7.验证结果 二、tags模块 &#xff08;一&#xff09;创建…

【自动驾驶|毫米波雷达】逻辑化讲清快时间与慢时间傅里叶变换

碎碎念&#xff1a;实习过程中发现在进行雷达知识交流时&#xff0c;大部分同事都会用英文简称代替中文的一些称呼&#xff0c;比如Chirp、FFT等等。起初我觉得是因为很多英伟达、TI芯片的开发教程都是英文的&#xff0c;所以看得多了大家都习惯这样称呼&#xff0c;后来在和指…

有没有不使用技术分析工具的?众汇10年交易经验发现还真有

不知道各位投资者有没有遇见过不使用技术分析工具的投资者&#xff0c;众汇用自己的10年外汇交易经验保证&#xff0c;别不信还真有&#xff0c;并且不在少数。 其实道理很简单&#xff0c;这些投资者不相信技术分析工具的效率!在这些投资者看来技术分析工具通常比较滞后、需要…

微信小程序开发-数据事件绑定

&#x1f433;简介 数据绑定 数据绑定是一种将小程序中的数据与页面元素关联起来的技术&#xff0c;使得当数据变化时&#xff0c;页面元素能够自动更新。这通常使用特定的语法&#xff08;如双大括号 {{ }}&#xff09;来实现&#xff0c;以便在页面上展示动态数据。 事件绑…

C++类和对象(基础篇)

前言&#xff1a; 其实任何东西&#xff0c;只要你想学&#xff0c;没人能挡得住你&#xff0c;而且其实学的也很快。那么本篇开始学习类和对象&#xff08;C的&#xff0c;由于作者有Java基础&#xff0c;可能有些东西过得很快&#xff09;。 struct在C中的含义&#xff1a; …

【Keil程序大小】Keil编译结果Code-RO-RW-ZI分析

【Keil程序大小】Keil编译结果Code-RO-RW-ZI分析 下图为keil编译后的结果&#xff1a; 单位为Byte。Code是程序大小。RO是常量大小。RW是读写变量占用大小&#xff0c;如已初始化的静态变量和全局变量。ZI是全零变量占用大小&#xff0c;如未初始化的static修饰的静态变量、全局…