JUC下的ScheduledThreadPoolExecutor详解

         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.mfbz.cn/a/604501.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

商务分析方法与工具(五):Python的趣味快捷-文件和文件夹操作自动化

Tips:"分享是快乐的源泉💧,在我的博客里,不仅有知识的海洋🌊,还有满满的正能量加持💪,快来和我一起分享这份快乐吧😊! 喜欢我的博客的话,记得…

pytest教程-41-钩子函数-pytest_runtest_teardown

领取资料,咨询答疑,请➕wei: June__Go 上一小节我们学习了pytest_runtest_call钩子函数的使用方法,本小节我们讲解一下pytest_runtest_teardown钩子函数的使用方法。 pytest_runtest_teardown 钩子函数在每个测试用例执行完成后被调用&…

游戏辅助 -- 三种分析角色坐标方法(CE、xdbg、龙龙遍历工具)

所用工具下载地址: https://pan.quark.cn/s/d54e7cdc55e6 在上次课程中,我们成功获取了人物对象的基址:[[[0xd75db8]1C]28],而人物血量的地址则是基址再加上偏移量278。 接下来,我们需要执行以下步骤来进一步操作&a…

JSP技术讲解

目录 1、JSP简介 2、JSP体验 3、JSP运行原理 4、JSP基本语法 5、JSP指令 6、JSP内置九大对象 7、JSP标签 8、JSP配置 9、JSP排错 10、总结 在前面的Servlet学习中发现Servlet本质是一个java程序,因此Servlet更加擅长编写程序的业务逻辑,而如果要…

BACnet到OPC UA的楼宇自动化系统与生产执行系统(MES)整合

在智能制造的浪潮下,一家位于深圳的精密电子制造企业面临着前所未有的挑战:如何高效地将楼宇自动化系统与生产执行系统(MES)整合,实现能源管理与生产流程的精细化控制。这家企业的楼宇控制系统使用的是BACnet协议&…

牛客NC97 字符串出现次数的TopK问题【中等 哈希+优先级队列 Java/Go】

题目 题目链接: https://www.nowcoder.com/practice/fd711bdfa0e840b381d7e1b82183b3ee 核心 哈希,优先级队列Java代码 import java.util.*;public class Solution {/*** 代码中的类名、方法名、参数名已经指定,请勿修改,直接返…

思维导图网页版哪个好?2024年值得推荐的8个在线思维导图软件!

思维导图如今已成为一种常用的工具,帮助我们清晰地组织和整理信息。随着科技的发展,思维导图的产品形态也经过多轮迭代,从最初的本地客户端过渡到基于云的 Web 端,各类网页版思维导图软件应运而生,它们方便快捷&#x…

【3dmax笔记】035: 车削修改器

一、车削修改器介绍 车削:图形通过绕轴旋转来创建三维效果。 开放的样条线,车削之后是面片。闭合的样条线,车削之后,是实体。 一、车削修改器实例 绘制高脚杯,首先在前视图绘制如下二维图形。 添加一个车削的修改器…

【Linux】shell基础,shell脚本

Shell Shell是一个用C语言编写的程序,接受用户输入的命令,并将其传递给操作系统内核执行。Shell还负责解释和执行命令、管理文件系统、控制进程,是用户使用Linux的桥梁。Shell既是一种命令语言,又是一种程序设计语言 Shell脚本 Sh…

HTML Audio标签src使用base64字符

源码&#xff1a; <!DOCTYPE html> <html> <head> <meta charset"utf-8"> <title>Audio src base64</title> </head> <body><audio controls><source src"data:audio/mp3;base64,//OIxAAAAAAAAAA…

2.小土堆——tensorboard使用

1.tensorboard是啥&#xff1f; TensorBoard 是一个用于可视化 TensorFlow 训练过程和模型的工具。它可以帮助你以图形和图表的形式查看训练过程中的指标&#xff0c;比如损失和准确率的变化。你可以使用 TensorBoard 来监视模型的性能&#xff0c;并且更直观地理解模型的工作原…

Classifier guidance与Classifier free diffusion的简单理解

参考&#xff1a;Classifier Guidance 和 Classifier Free Guidance&#xff0c;一堆公式不如两行代码 - 蓟梗的文章 - 知乎 https://zhuanlan.zhihu.com/p/660518657 Classifier Guidance和Classifier-free Guidance 总结 - 走遍山水路的文章 - 知乎 https://zhuanlan.zhihu.c…

【雅思写作】Vince9120雅思小作文笔记——P1 Intro(前言)

文章目录 链接P1 Intro&#xff08;前言&#xff09;字数限制题型综述&#xff08;problem types overview&#xff09;1. **柱状图&#xff08;Bar Chart&#xff09;** - 描述不同类别在某个或多个变量上的数据量比较。2. **线图&#xff08;Line Graph&#xff09;** - 展示…

Lib city笔记:TrajectoryDataset

1 AbstractDataset 抽象类&#xff0c;所有数据集的基类 2 TrajectoryDataset 2.1 __init__ 2.2 get_data 2.3 cutter_filter 2.3.1 按照时间间隔切割 2.3.2 按照同一天切割 2.3.3 按照固定窗口长度切割 cut完的轨迹样子 每一个key是一个轨迹的id&#xff0c;对应的value内容…

SQL查询语句(三)范围查找关键字

在上一篇文章中&#xff0c;我们介绍了SQL语句中&#xff0c;逻辑关键字的作用&#xff0c;并举例演示了如何用逻辑关键字来组合WHERE子句。在文章的末尾我们提到了两个用于范围查找的关键字IN和BETWEEN。这两个关键字都可以与NOT关键字灵活组合&#xff0c;起到对字句结果取反…

Crowd counting 系列NO.2—MCNN

声明&#xff1a;博客是用latex写的&#xff0c;所以直接用图片来展示吧&#xff0c;效果是一样的。下载资源网上都很容易搜到&#xff0c;如需下载资源&#xff0c;请留言。

Java内存是怎样分配的

Java内存是怎样分配的 一、 1. 有些编程语言编写的程序会直接向操作系统请求内存&#xff0c;而 Java 语言为保证其平台无关性&#xff0c;并不允许程序直接向操作系统发出请求&#xff0c;而是在准备执行程序时由Java虚拟机&#xff08;JVM&#xff09;向操作系统请求一定的…

基于SpringBoot+Vue点餐系统设计和实现(源码+LW+部署讲解)

&#x1f339;作者简介&#xff1a;✌全网粉丝10W&#xff0c;前大厂员工&#xff0c;多篇互联网电商推荐系统专利&#xff0c;现有多家创业公司&#xff0c;致力于建站、运营、SEO、网赚等赛道。也是csdn特邀作者、博客专家、Java领域优质创作者&#xff0c;博客之星、掘金/华…

VxTerm使用教程:连接SSH服务端设备,什么是SSH

一、什么是SSH&#xff1f; <摘自百度> 安全外壳协议 SSH&#xff0c;即安全外壳协议&#xff08;Secure Shell&#xff09;&#xff0c;是一种网络协议&#xff0c;用于在计算机网络上提供安全的远程登录和命令执行功能。 SSH通过加密通信通道来保护数据传输&#xff0c…

C++:类与对象—继承

类与对象—继承 一、继承是什么&#xff1f;二、继承定义三、基类和派生类对象赋值转换四、继承中的作用域五、派生类的默认成员函数六、继承与友元七、继承与静态成员八、复杂的菱形继承及菱形虚拟继承九、继承的总结和反思十、考察重点 一、继承是什么&#xff1f; 继承(inh…