当前位置: 首页 > news >正文

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实现延时队列&#xff0c;但是底层的定时器不止如何实现好些&#xff0c;故此研究了一下jdk的Timer。 Timer是一个用于执行定时任务的类&#xff0c;可以单次执行或按指定时间间隔循环执行&#xff08;直到主动cancel或线程被杀掉&#xff09;。Ti…...

vue3中使用swiper完整版教程

介绍 在 vue3 中使用 swiper, 实现轮播图的效果&#xff1b;如果组件样式等模块引入不当&#xff0c;很有可能导致&#xff0c;页面无效果&#xff1b;或者想要的箭头或者切换效果异常问题。具体使用方式如下所示&#xff1a; 使用方式 使用命令 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 框架的开源应用程序开发工具&#xff0c;它旨在简化 Spring 应用程序的配置和开发过程。Spring Boot 提供了一种简单的方式来创建可独立运行的、生产级别的应用程序&#xff0c;并在需要时进行部署。Spring Boot 在微服务架构和云计算环境下得到…...

Git - 在主分支上创建分支并提交代码

拉取最新代码 因为当前在 master 分支下&#xff0c;你必须拉取最新代码&#xff0c;保证当前代码与线上同步&#xff08;最新&#xff09;&#xff0c;执行以下命令&#xff1a; git pull origin master创建分支 目前我们在 master 主分支上&#xff0c;需要执行以下命令&…...

第三届无线通信AI大赛分享交流会暨颁奖典礼顺利举办,大赛圆满收官

2月16日&#xff0c;第三届无线通信AI大赛分享交流会暨颁奖典礼在北京顺利举行&#xff0c;宣告大赛圆满收官。 分享交流会暨颁奖典礼以线上线下结合的形式展开&#xff0c;邀请无线通信领域的多位专家、学者与「基于AI的信道估计与信道状态信息反馈联合设计」、「基于AI的高精…...

程序的本质与类的说明

摘自《深入浅出WPF完整版》P132程序的本质就是“数据算法”&#xff0c;&#xff0c;这一本质一直没有改变一一类的作用只是把散落在程序中的变量和函数进行归档封装并控制对它们的访问而已。被封装在类里的变量称为字段 (Fied)它表示的是类或实例的状态:被封装在类里的函数称为…...

单片机——显示方式

数码LED 一、静态显示方式 1、连接 所有LED的位选均共同连接到VCC或GND&#xff0c;每个LED的8根段选线分别连接一个8位并行I/O口&#xff0c;从该I/O口送出相应的字型码显示字型。 2、这种连接方式的缺点就是需要的数据线太多&#xff1a;我们可以计算一下&#xff1a;8*4133根…...

leetcode 1~10 学习经历

LeetCode 习题 1 - 101. 两数之和2. 两数相加3. 无重复字符的最长子串4. 寻找两个正序数组的中位数5. 最长回文子串6. N 字形变换7. 整数反转8. 字符串转换整数 (atoi)9. 回文数10. 正则表达式匹配1. 两数之和 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在…...

代码质量与安全 | 一文了解高级驾驶辅助系统(ADAS)及其开发中需要遵循的标准

高级驾驶辅助系统&#xff08;ADAS&#xff09;有助于提高车内每个人的安全性&#xff0c;帮助他们安全抵达目的地。这项技术功能非常重要&#xff0c;因为大多数的严重车祸都是人为错误造成的。 本篇文章将讨论什么是高级驾驶辅助系统&#xff08;ADAS&#xff09;&#xff0…...

2023年安徽省职业院校“磐云杯”网络安全竞赛任务书

2023年安徽省职业院校“磐云杯”网络安全竞赛 任务书 一、竞赛时间 总计&#xff1a;360分钟 三、竞赛任务书内容 &#xff08;一&#xff09;拓扑图 &#xff08;二&#xff09;A模块基础设施设置/安全加固&#xff08;200分&#xff09; 一、项目和任务描述&#xff1a;…...

九龙证券|豪掷超6000万,10转3派6元,今年第二只高送转股出炉!

新瀚新材高送转发布计划&#xff0c;股价年初以来大涨超50%。航运板块6股自2022年低点股价翻倍。 2月17日晚间&#xff0c;凯瑞德、新瀚新材2家公司发布了2022年年报&#xff1b;一起&#xff0c;新瀚新材高送转计划同步出炉。 报告显现&#xff0c;2022年度新瀚新材营业总收入…...

Java开发 - 数风流人物,还看“微服务”

目录 前言 服务器端的发展历程 早期的服务器 动态的页面 用户内容网站 微服务 企业级应用 互联网应用 微服务介绍 什么是微服务&#xff1f; 为什么使用微服务 怎么使用微服务 Spring Cloud 什么是Spring Cloud Nacos注册中心 什么是Nacos 创建微服务项目 创建…...

Springboot 整合 分布式定时任务 XXL-JOB

起因 恰逢周末&#xff0c; 最近公司接入了分布式定时任务&#xff0c;我是负责接入这块的&#xff0c;正好在网上想起了之前看过的分布式任务的文章&#xff0c;然后学习一下 各路框架发现看了很多框架比如 elasticjob 跟xxl-job不同的是&#xff0c;elasticjob是采用zookeepe…...

细谈JavaWeb中的Request和Response

文章目录1&#xff0c;Request和Response的概述2&#xff0c;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注解&#xff0c;帮助我们省略了Setter,Getter,ToString等注解&#xff0c;一般对于普通的实体类使用该注解&#xff0c;不会出现什么问题&#xff0c;但是当我们把这个注解&#xff0c;使用在派生类上&#xff0c;就出现了一个警告1 情景再现父类:Data …...

linux环境搭建私有gitlab仓库以及启动gitlab后出现卡顿处理办法

搭建之前&#xff0c;需要安装相应的依赖包&#xff0c;并且要启动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. 云管理服务&#xff08;MSP&#xff09;市场定义 3. 厂商评估&#xff1a;华创方舟 4. 入选证书 1. 研究范围定义 数字化时代&#xff0c;应用成为企业开展各项业务的落脚点。随着业务的快速发展&#xff0c;应用的功能迭代变得越来越…...

力扣-部门工资前三高的所有员工

大家好&#xff0c;我是空空star&#xff0c;本篇带大家了解一道稍微复杂的力扣sql练习题。 文章目录前言一、题目&#xff1a;185. 部门工资前三高的所有员工二、解题1.正确示范①提交SQL运行结果2.正确示范②提交SQL运行结果3.其他总结前言 上一篇带大家练习了部门工资最高的…...

山东大学教授团畅谈ChatGPT革命座谈会,探讨ChatGPT发展趋势

2月18日&#xff0c;由山东大学多院系教授学者组成的山东大学教授团在济南福瑞达自贸创新产业园举行了“畅谈ChatGPT革命”座谈会&#xff0c;诸位教授学者就ChatGPT出现的影响进行了探讨。产业园首席顾问李铁岗教授向大家介绍产业园区山东大学经济学院教授、济南福瑞达自贸创新…...

如何在看板中有效管理突发紧急任务

在看板中有效管理突发紧急任务需要&#xff1a;设立专门的紧急任务通道、重新调整任务优先级、保持适度的WIP&#xff08;Work-in-Progress&#xff09;弹性、优化任务处理流程、提高团队应对突发情况的敏捷性。其中&#xff0c;设立专门的紧急任务通道尤为重要&#xff0c;这能…...

C++中string流知识详解和示例

一、概览与类体系 C 提供三种基于内存字符串的流&#xff0c;定义在 <sstream> 中&#xff1a; std::istringstream&#xff1a;输入流&#xff0c;从已有字符串中读取并解析。std::ostringstream&#xff1a;输出流&#xff0c;向内部缓冲区写入内容&#xff0c;最终取…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作

一、上下文切换 即使单核CPU也可以进行多线程执行代码&#xff0c;CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短&#xff0c;所以CPU会不断地切换线程执行&#xff0c;从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...

什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南

文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/55aefaea8a9f477e86d065227851fe3d.pn…...

【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分

一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计&#xff0c;提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合&#xff1a;各模块职责清晰&#xff0c;便于独立开发…...

【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)

1.获取 authorizationCode&#xff1a; 2.利用 authorizationCode 获取 accessToken&#xff1a;文档中心 3.获取手机&#xff1a;文档中心 4.获取昵称头像&#xff1a;文档中心 首先创建 request 若要获取手机号&#xff0c;scope必填 phone&#xff0c;permissions 必填 …...

NXP S32K146 T-Box 携手 SD NAND(贴片式TF卡):驱动汽车智能革新的黄金组合

在汽车智能化的汹涌浪潮中&#xff0c;车辆不再仅仅是传统的交通工具&#xff0c;而是逐步演变为高度智能的移动终端。这一转变的核心支撑&#xff0c;来自于车内关键技术的深度融合与协同创新。车载远程信息处理盒&#xff08;T-Box&#xff09;方案&#xff1a;NXP S32K146 与…...

C++课设:简易日历程序(支持传统节假日 + 二十四节气 + 个人纪念日管理)

名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏介绍:《编程项目实战》 目录 一、为什么要开发一个日历程序?1. 深入理解时间算法2. 练习面向对象设计3. 学习数据结构应用二、核心算法深度解析…...

接口自动化测试:HttpRunner基础

相关文档 HttpRunner V3.x中文文档 HttpRunner 用户指南 使用HttpRunner 3.x实现接口自动化测试 HttpRunner介绍 HttpRunner 是一个开源的 API 测试工具&#xff0c;支持 HTTP(S)/HTTP2/WebSocket/RPC 等网络协议&#xff0c;涵盖接口测试、性能测试、数字体验监测等测试类型…...

如何在Windows本机安装Python并确保与Python.NET兼容

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…...