JDK定时器Timer原理
前言
前些时间想到利用redis实现延时队列,但是底层的定时器不止如何实现好些,故此研究了一下jdk的Timer。
Timer是一个用于执行定时任务的类,可以单次执行或按指定时间间隔循环执行(直到主动cancel或线程被杀掉)。Timer中任务处理采用了生产者-消费者模型的设计思想。
原理简述
Timer里面维护了一个TimerThread(继承了Thread)和TaskQueue(其实就是一个初始化长度为128的TimerTask数组【TimerTask就是我们的执行任务】),当我们new Timer()的时候就会调用TimerThread.start方法,然后TimerThread线程while(true)循环从TaskQueue中取出任务(TimerTask)执行,那么如何取出执行时间最近的任务呢?前面有提到TaskQueue底层是一个数组,但其实是个小根堆,只要每次取出根元素就是执行时间最近的一个任务了。
ps:TimerTask里面有一个long类型的nextExecutionTime(下次执行时间)变量,小根堆就是根据这个来排序的…
在这里取出最近的任务并不是取的小根堆的根元素,而是TaskQueue中下标为1的元素,也就是第二个元素
组件介绍
Timer关键属性:TaskQueue、TimerThread
TaskQueue:任务队列
一个存储任务的任务池TaskQueue,包含一个初始大小为128的TimerTask数组,负责任务的存储(add)、排序(fixUp、fixDown)、取出(getMin)、清理(removeMin、quickRemove)、循环任务处理(rescheduleMin)以及一些其他基本操作。并通过排序保证队头任务的执行一定是最早的。
根据注释可以知道TimerTask是一个用做一个平衡二叉树的模型,一个父节点array[n]下挂载的两个子节点为array[2n]和array[2n+1].

TimerTask:任务实体
TimerTask是任务实体,Runnable接口的实现类。内部包含用于线程安全的锁lock、用于标记任务状态的字段state、以及一个供用户实现的任务内容抽象方法run().
public abstract class TimerTask implements Runnable {/** 此对象用于控制对TimerTask内部的访问。 */final Object lock = new Object();/** 标记任务状态的字段 初始化为0 */int state = VIRGIN;/** 该状态表示任务尚未执行。在TimerTask对象创建时,它的状态就是VIRGIN。*/static final int VIRGIN = 0;/** * 表示任务已经被调度,等待执行。当调用Timer.schedule()方法成功后,任务的状态将变为SCHEDULED。* 此时任务已经被加入了任务队列中,等待Timer线程按照任务的调度时间来执行。*/static final int SCHEDULED = 1;/** 表示任务已经执行完成。此时任务的run()方法已经执行完毕,但任务对象还没有从任务队列中删除,因为队列中的任务删除是由Timer线程自动完成的。*/static final int EXECUTED = 2;/** * 该状态表示任务已经被取消。当调用TimerTask.cancel()方法取消任务时,任务的状态将变为CANCELLED。* 此时任务被标记为取消状态,即使它已经被加入了任务队列中,也不会执行。一旦任务被标记为CANCELLED状态,它将永远不会被执行。*/static final int CANCELLED = 3;/*** 此任务的下一次执行时间,格式为System.currentTimeMillis,假设此任务计划执行。对于重复任务,此字段在每次任务执行之前更新。*/long nextExecutionTime;/*** 重复任务的周期(毫秒)。正值表示固定利率执行。负值表示固定延迟执行。值0表示非重复任务。*/long period = 0;// ...
TimerThread:事件消费者
一个作为事件消费者的TimerThread,TimerThread中不断获取当前任务队列的队头任务,执行任务。并根据任务是否需要循环决定是移除任务还是将任务按下一次执行时间重新加入到任务队列中。在TimerThread中不断获取待执行任务时,采用了Object.wait()和Object.notify()的机制。Object.wait()保证了任务队列为空时及时释放资源,而当有新的任务时也通过Object.notify()及时恢复任务的遍历。

Timer本身提供了对以上三者操作的封装、实例化和对外暴露运行任务的接口。同时,作为生产者,将用户任务加入到任务队列;对消费者层面,Timer也是和消费者线程唯一绑定的,负责启动消费者线程,并在生产了新的任务后及时通知已经休眠的消费者。提供了多种构造方法和清理接口。
源码解析
Timer的使用demo
public class TimerDemo {public static void main(String[] args) {SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");Timer timer = new Timer();timer.schedule(new TimerTask() {@Overridepublic void run() {String strDate = format.format(new Date());try {Thread.sleep(1000);} catch (InterruptedException e) { }System.err.println("strDate1 = " + strDate);}}, 5, 5000);}}
Timer构造方法:
// Timer.java 131行
public Timer() {this("Timer-" + serialNumber());
}
// Timer.java 158行
public Timer(String name) {// 设置线程名称thread.setName(name);// 启动TimerThread线程thread.start();
}
TimerThread继承了Thread类,所以我们看一下它的run方法:
// Timer.java 503行
public void run() {try {mainLoop();} finally {// Someone killed this Thread, behave as if Timer cancelled// 【有人杀死了这个线程,表现得就像计时器被取消一样】synchronized(queue) {newTasksMayBeScheduled = false;queue.clear(); // Eliminate obsolete references【消除过时的引用】}}
}
跟进去mainLoop()方法
/*** 主计时器循环*/// Timer.java 518行
private void mainLoop() {while (true) {try {TimerTask task;boolean taskFired;synchronized(queue) {// 线程为空则waitwhile (queue.isEmpty() && newTasksMayBeScheduled)queue.wait();if (queue.isEmpty())break; // 队列为空,将永远保留;死亡long currentTime, executionTime;// 获取距离执行时间最近的任务(返回的是queue[1]的引用)task = queue.getMin();synchronized(task.lock) {// 如果任务的状态是已经取消,则移除该任务,移除后TimerTask数组元素将重新排序if (task.state == TimerTask.CANCELLED) {queue.removeMin();continue; // No action required, poll queue again}currentTime = System.currentTimeMillis();// 该任务的下次执行时间executionTime = task.nextExecutionTime;// 若任务的下次执行时间 < 当前时间if (taskFired = (executionTime<=currentTime)) {// period值0表示非重复任务。将该任务从任务队列移除,并且将任务的state设置为已执行if (task.period == 0) { // Non-repeating, removequeue.removeMin();task.state = TimerTask.EXECUTED;} else { // Repeating task, reschedule// 将与头任务关联的nextExecutionTime设置为指定值,并相应地调整优先级队列。queue.rescheduleMin(task.period<0 ? currentTime - task.period: executionTime + task.period);}}}// 还未到执行时间,waitif (!taskFired)queue.wait(executionTime - currentTime);}// 执行时间到了,执行任务的run方法【这里就是我们自己写的逻辑了】if (taskFired)task.run();} catch(InterruptedException e) {}}
}
如上,若queue为空&&newTasksMayBeScheduled为true,则调用wait方法等待,若queue不为空则调用queue.getMin获取距离执行时间最近的任务(返回的是queue[1]的引用),然后判断任务的state和nextExecutionTime(下次执行时间),条件满足则执行任务;如果任务的state是已经取消,则移除该任务,移除后TimerTask数组元素将重新排序,若还未到执行时间则wait。
ps:为什么queue.getMin是返回queue[1]元素?
答:TaskQueue内部使用一个数组queue来存储所有的TimerTask对象。数组的第一个元素queue[0]没有使用,实际的任务从数组的第二个元素queue[1]开始存储。数组中的元素是按照任务的执行时间从小到大排序的,也就是说,queue[1]元素表示最先执行的任务。
接下来再看Timer.schedule()方法:
public void schedule(TimerTask task, long delay, long period) {if (delay < 0)throw new IllegalArgumentException("Negative delay.");if (period <= 0)throw new IllegalArgumentException("Non-positive period.");// 进去看看,参数1:任务 参数二:下次执行时间 参数三:重复任务的周期(毫秒)sched(task, System.currentTimeMillis()+delay, -period);
}// Timer.java 386行
private void sched(TimerTask task, long time, long period) {if (time < 0)throw new IllegalArgumentException("Illegal execution time.");// Constrain value of period sufficiently to prevent numeric// overflow while still being effectively infinitely large.if (Math.abs(period) > (Long.MAX_VALUE >> 1))period >>= 1;synchronized(queue) {if (!thread.newTasksMayBeScheduled)throw new IllegalStateException("Timer already cancelled.");synchronized(task.lock) {if (task.state != TimerTask.VIRGIN)throw new IllegalStateException("Task already scheduled or cancelled");// 设置任务下次执行时间、任务周期、任务状态task.nextExecutionTime = time;task.period = period;task.state = TimerTask.SCHEDULED;}// 将任务添加到队列【必要的时候会对任务队列进行扩容,对小根堆的元素重新排序】queue.add(task);// 若当前添加的任务是距离当前执行时间最近的任务则唤醒等待线程【其实就是TimerThread线程】if (queue.getMin() == task)queue.notify();}
}
最后有几点需要注意:
1)在不使用时一定要及时cancel清理,释放资源。
2)当timer中有多任务时,因为后边任务会依赖前边任务执行完,尤其是如果有耗时任务,会发生定时不准确的现象。
3)当存在多任务时,若其中某个因异常而终止,则会退出所有任务的执行(消费者线程被异常终止了)
相关文章:
JDK定时器Timer原理
前言 前些时间想到利用redis实现延时队列,但是底层的定时器不止如何实现好些,故此研究了一下jdk的Timer。 Timer是一个用于执行定时任务的类,可以单次执行或按指定时间间隔循环执行(直到主动cancel或线程被杀掉)。Ti…...
vue3中使用swiper完整版教程
介绍 在 vue3 中使用 swiper, 实现轮播图的效果;如果组件样式等模块引入不当,很有可能导致,页面无效果;或者想要的箭头或者切换效果异常问题。具体使用方式如下所示: 使用方式 使用命令 npm install swiper 安装 sw…...
某个div的滚动条样式
.item-body,.chart2{/*滚动条整体样式*/&::-webkit-scrollbar {/*高宽分别对应横竖滚动条的尺寸*/width: 10px;height: 1px;}/*滚动条里面小方块*/&::-webkit-scrollbar-thumb {border-radius: 10px;-webkit-box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);background:…...
Spring Boot框架基础介绍
Spring Boot 是一款基于 Spring 框架的开源应用程序开发工具,它旨在简化 Spring 应用程序的配置和开发过程。Spring Boot 提供了一种简单的方式来创建可独立运行的、生产级别的应用程序,并在需要时进行部署。Spring Boot 在微服务架构和云计算环境下得到…...
Git - 在主分支上创建分支并提交代码
拉取最新代码 因为当前在 master 分支下,你必须拉取最新代码,保证当前代码与线上同步(最新),执行以下命令: git pull origin master创建分支 目前我们在 master 主分支上,需要执行以下命令&…...
第三届无线通信AI大赛分享交流会暨颁奖典礼顺利举办,大赛圆满收官
2月16日,第三届无线通信AI大赛分享交流会暨颁奖典礼在北京顺利举行,宣告大赛圆满收官。 分享交流会暨颁奖典礼以线上线下结合的形式展开,邀请无线通信领域的多位专家、学者与「基于AI的信道估计与信道状态信息反馈联合设计」、「基于AI的高精…...
程序的本质与类的说明
摘自《深入浅出WPF完整版》P132程序的本质就是“数据算法”,,这一本质一直没有改变一一类的作用只是把散落在程序中的变量和函数进行归档封装并控制对它们的访问而已。被封装在类里的变量称为字段 (Fied)它表示的是类或实例的状态:被封装在类里的函数称为…...
单片机——显示方式
数码LED 一、静态显示方式 1、连接 所有LED的位选均共同连接到VCC或GND,每个LED的8根段选线分别连接一个8位并行I/O口,从该I/O口送出相应的字型码显示字型。 2、这种连接方式的缺点就是需要的数据线太多:我们可以计算一下:8*4133根…...
leetcode 1~10 学习经历
LeetCode 习题 1 - 101. 两数之和2. 两数相加3. 无重复字符的最长子串4. 寻找两个正序数组的中位数5. 最长回文子串6. N 字形变换7. 整数反转8. 字符串转换整数 (atoi)9. 回文数10. 正则表达式匹配1. 两数之和 给定一个整数数组 nums 和一个整数目标值 target,请你在…...
代码质量与安全 | 一文了解高级驾驶辅助系统(ADAS)及其开发中需要遵循的标准
高级驾驶辅助系统(ADAS)有助于提高车内每个人的安全性,帮助他们安全抵达目的地。这项技术功能非常重要,因为大多数的严重车祸都是人为错误造成的。 本篇文章将讨论什么是高级驾驶辅助系统(ADAS)࿰…...
2023年安徽省职业院校“磐云杯”网络安全竞赛任务书
2023年安徽省职业院校“磐云杯”网络安全竞赛 任务书 一、竞赛时间 总计:360分钟 三、竞赛任务书内容 (一)拓扑图 (二)A模块基础设施设置/安全加固(200分) 一、项目和任务描述:…...
九龙证券|豪掷超6000万,10转3派6元,今年第二只高送转股出炉!
新瀚新材高送转发布计划,股价年初以来大涨超50%。航运板块6股自2022年低点股价翻倍。 2月17日晚间,凯瑞德、新瀚新材2家公司发布了2022年年报;一起,新瀚新材高送转计划同步出炉。 报告显现,2022年度新瀚新材营业总收入…...
Java开发 - 数风流人物,还看“微服务”
目录 前言 服务器端的发展历程 早期的服务器 动态的页面 用户内容网站 微服务 企业级应用 互联网应用 微服务介绍 什么是微服务? 为什么使用微服务 怎么使用微服务 Spring Cloud 什么是Spring Cloud Nacos注册中心 什么是Nacos 创建微服务项目 创建…...
Springboot 整合 分布式定时任务 XXL-JOB
起因 恰逢周末, 最近公司接入了分布式定时任务,我是负责接入这块的,正好在网上想起了之前看过的分布式任务的文章,然后学习一下 各路框架发现看了很多框架比如 elasticjob 跟xxl-job不同的是,elasticjob是采用zookeepe…...
细谈JavaWeb中的Request和Response
文章目录1,Request和Response的概述2,Request对象2.1 Request继承体系2.2 Request获取请求数据2.2.1 获取请求行数据2.2.2 获取请求头数据2.2.3 获取请求体数据2.2.4 获取请求参数的通用方式2.4 请求参数中文乱码问题2.4.1 POST请求解决方案2.4.2 GET请求…...
lombok注解@Data使用在继承类上时出现警告解决方案
lombok为我们提供了Data注解,帮助我们省略了Setter,Getter,ToString等注解,一般对于普通的实体类使用该注解,不会出现什么问题,但是当我们把这个注解,使用在派生类上,就出现了一个警告1 情景再现父类:Data …...
linux环境搭建私有gitlab仓库以及启动gitlab后出现卡顿处理办法
搭建之前,需要安装相应的依赖包,并且要启动sshd服务(1).安装policycoreutils-python openssh-server openssh-clients [rootVM-0-2-centos ~]# sudo yum install -y curl policycoreutils-python openssh-server openssh-clients [rootVM-0-2-centos ~]…...
2023爱分析· 云管理服务(MSP)市场厂商评估报告:华创方舟
目录 1. 研究范围定义 2. 云管理服务(MSP)市场定义 3. 厂商评估:华创方舟 4. 入选证书 1. 研究范围定义 数字化时代,应用成为企业开展各项业务的落脚点。随着业务的快速发展,应用的功能迭代变得越来越…...
力扣-部门工资前三高的所有员工
大家好,我是空空star,本篇带大家了解一道稍微复杂的力扣sql练习题。 文章目录前言一、题目:185. 部门工资前三高的所有员工二、解题1.正确示范①提交SQL运行结果2.正确示范②提交SQL运行结果3.其他总结前言 上一篇带大家练习了部门工资最高的…...
山东大学教授团畅谈ChatGPT革命座谈会,探讨ChatGPT发展趋势
2月18日,由山东大学多院系教授学者组成的山东大学教授团在济南福瑞达自贸创新产业园举行了“畅谈ChatGPT革命”座谈会,诸位教授学者就ChatGPT出现的影响进行了探讨。产业园首席顾问李铁岗教授向大家介绍产业园区山东大学经济学院教授、济南福瑞达自贸创新…...
装饰模式(Decorator Pattern)重构java邮件发奖系统实战
前言 现在我们有个如下的需求,设计一个邮件发奖的小系统, 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其…...
synchronized 学习
学习源: https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖,也要考虑性能问题(场景) 2.常见面试问题: sync出…...
VB.net复制Ntag213卡写入UID
本示例使用的发卡器:https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...
【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密
在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...
基于当前项目通过npm包形式暴露公共组件
1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹,并新增内容 3.创建package文件夹...
Keil 中设置 STM32 Flash 和 RAM 地址详解
文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...
Device Mapper 机制
Device Mapper 机制详解 Device Mapper(简称 DM)是 Linux 内核中的一套通用块设备映射框架,为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程,并配以详细的…...
短视频矩阵系统文案创作功能开发实践,定制化开发
在短视频行业迅猛发展的当下,企业和个人创作者为了扩大影响力、提升传播效果,纷纷采用短视频矩阵运营策略,同时管理多个平台、多个账号的内容发布。然而,频繁的文案创作需求让运营者疲于应对,如何高效产出高质量文案成…...
AirSim/Cosys-AirSim 游戏开发(四)外部固定位置监控相机
这个博客介绍了如何通过 settings.json 文件添加一个无人机外的 固定位置监控相机,因为在使用过程中发现 Airsim 对外部监控相机的描述模糊,而 Cosys-Airsim 在官方文档中没有提供外部监控相机设置,最后在源码示例中找到了,所以感…...
08. C#入门系列【类的基本概念】:开启编程世界的奇妙冒险
C#入门系列【类的基本概念】:开启编程世界的奇妙冒险 嘿,各位编程小白探险家!欢迎来到 C# 的奇幻大陆!今天咱们要深入探索这片大陆上至关重要的 “建筑”—— 类!别害怕,跟着我,保准让你轻松搞…...
