模拟实现Java中的计时器
定时器是什么
定时器也是软件开发中的⼀个重要组件. 类似于⼀个 "闹钟". 达到⼀个设定的时间之后, 就执⾏某个指定好的代码. 前端/后端中都会用到计时器.
定时器是⼀种实际开发中⾮常常⽤的组件. ⽐如⽹络通信中, 如果对⽅ 500ms 内没有返回数据, 则断开连接尝试重连. ⽐如⼀个 Map, 希望⾥⾯的某个 key 在 3s 之后过期(⾃动删除). 类似于这样的场景就需要⽤到定时器.
标准库中的定时器
• 标准库中提供了⼀个 Timer 类. Timer 类的核⼼⽅法为 schedule .
• schedule 包含两个参数. 第⼀个参数指定即将要执⾏的任务代码, 第⼆个参数指定多⻓时间之后 执⾏ (单位为毫秒).
// 定时器的使用
public class Demo21 {public static void main(String[] args) {Timer timer = new Timer();// main 方法中调用 timer.schedule 方法时, // 它只是将任务注册到 Timer 中,并告诉 Timer // 在 3000 毫秒后执行这个任务。// 任务的执行是由 Timer 内部的守护线程完成的。timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("hello 3");}}, 3000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("hello 2");}}, 2000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("hello 1");}}, 1000);System.out.println("程序开始执行!");}
}

模拟实现定时器

那么该怎么解决呢?

class MyTimerTask {// 任务啥时候执行. 毫秒级的时间戳.private long time;// 任务具体是啥.private Runnable runnable;public long getTime() {return time;}public Runnable getRunnable() {return runnable;}public MyTimerTask(Runnable runnable, long delay) {// delay 是一个相对的时间差. 形如 3000 这样的数值.// 构造 time 要根据当前系统时间和 delay 进行构造.time = System.currentTimeMillis() + delay;this.runnable = runnable;}
}// 定时器的本体
class MyTimer {// 使用优先级队列 来保存上述的N个任务private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();// 定时器的核心方法 就是把要执行的任务添加到队列中public void schedule(Runnable runnable, long delay) {MyTimerTask task = new MyTimerTask(runnable, delay);queue.offer(task);}// MyTimer 中还需要构造一个 "扫描线程", 一方面去负责监控队首元素是否到点了,// 是否应该执行;// 一方面当任务到点之后,就要调用这里的 Runnable 的 Run 方法来完成任务public MyTimer() {// 扫描线程Thread t1 = new Thread(() -> {// 不停地去扫描当前的队首元素while (true) {try {if (queue.isEmpty()) {continue;}MyTimerTask task = queue.peek();long curTime = System.currentTimeMillis();if (curTime > task.getTime()) {// 假设当前时间是 14:01, 任务时间是 14:00, // 此时就意味着应该要执行这个任务了.// 需要执行任务.queue.poll();task.getRunnable().run();}else {// 让当前线程休眠一下, 按照时间差来休眠.Thread.sleep(task.getTime() - curTime);}}catch (InterruptedException e) {e.printStackTrace();}}});t1.start();}
}
上述代码写完了计时器的核心逻辑, 但是这份代码中还有几个关键性的问题.








最后完整的模拟实现代码.
import java.util.PriorityQueue;
import java.util.Timer;/*** Created with IntelliJ IDEA.* Description:* User: xiaotutu* Date: 2025-02-20* Time: 21:41*/class MyTimerTask implements Comparable<MyTimerTask>{// 任务啥时候执行. 毫秒级的时间戳.private long time;// 任务具体是啥.private Runnable runnable;public long getTime() {return time;}public Runnable getRunnable() {return runnable;}public MyTimerTask(Runnable runnable, long delay) {// delay 是一个相对的时间差. 形如 3000 这样的数值.// 构造 time 要根据当前系统时间和 delay 进行构造.time = System.currentTimeMillis() + delay;this.runnable = runnable;}@Overridepublic int compareTo(MyTimerTask o) {// 认为时间小的, 优先级高. 最终时间最小的元素, 就会放到队首.// 怎么记忆, 这里是谁减去谁?? 不要记!! 记容易记错~~// 随便写一个顺序, 然后实验一下就行了.return (int) (this.time - o.time);// return (int) (o.time - this.time);}
}// 定时器的本体
class MyTimer {// 使用优先级队列 来保存上述的N个任务private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();// 用来加锁的对象private Object locker = new Object();// 定时器的核心方法 就是把要执行的任务添加到队列中public void schedule(Runnable runnable, long delay) {synchronized (locker) {MyTimerTask task = new MyTimerTask(runnable, delay);queue.offer(task);// 每次来新的任务, 都唤醒一下之前的扫描线程. // 好让扫描线程根据最新的任务情况, 重新规划等待时间.locker.notify();}}// MyTimer 中还需要构造一个 "扫描线程", 一方面去负责监控队首元素是否到点了, // 是否应该执行;// 一方面当任务到点之后,就要调用这里的 Runnable 的 Run 方法来完成任务public MyTimer() {// 扫描线程Thread t1 = new Thread(() -> {// 不停地去扫描当前的队首元素while (true) {try {synchronized (locker) {while (queue.isEmpty()) {// 注意, 当前如果队列为空, 此时就不应该去取这里的// 元素. 此处使用 wait 等待更合适. // 如果使用 continue, 就会使这个线程// while 循环运行的飞快,// 也会陷入一个高频占用 cpu 的状态(忙等).//continue;locker.wait();}MyTimerTask task = queue.peek();long curTime = System.currentTimeMillis();if (curTime > task.getTime()) {// 假设当前时间是 14:01, 任务时间是 14:00, 此时就// 意味着应该要执行这个任务了.// 需要执行任务.queue.poll();task.getRunnable().run();}else {// 让当前线程休眠一下, 按照时间差来休眠.// Thread.sleep(task.getTime() - curTime);locker.wait(task.getTime() - curTime);}}}catch (InterruptedException e) {e.printStackTrace();}}});t1.start();}
}public class Demo22 {public static void main(String[] args) {MyTimer timer = new MyTimer();timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hello 3");}}, 3000);timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hello 2");}}, 2000);timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hello 1");}}, 1000);System.out.println("程序开始运行");}
}
相关文章:
模拟实现Java中的计时器
定时器是什么 定时器也是软件开发中的⼀个重要组件. 类似于⼀个 "闹钟". 达到⼀个设定的时间之后, 就执⾏某个指定好的代码. 前端/后端中都会用到计时器. 定时器是⼀种实际开发中⾮常常⽤的组件. ⽐如⽹络通信中, 如果对⽅ 500ms 内没有返回数据, 则断开连接尝试重…...
Eclipse2024中文汉化教程(图文版)
对应Eclipse,部分人需要中文汉化,本章教程,介绍如何对Eclipse进行汉化的具体步骤。 一、汉化前的Eclipse 默认安装Eclipse的时候,默认一般都是English的,我当前版本是使用的是2024-06版本的Eclipse。 二、汉化详细步骤 点击上方菜单选项卡,Hep——Install New Software……...
【回溯算法2】
力扣17.电话号码的字母组合 链接: link 思路 这道题容易想到用嵌套的for循环实现,但是如果输入的数字变多,嵌套的for循环也会变长,所以暴力破解的方法不合适。 可以定义一个map将数字和字母对应,这样就可以获得数字字母的映射了…...
21.《SpringBoot 异步编程@Async与CompletableFuture》
SpringBoot 异步编程 文章导读 本文系统讲解 Spring Boot 异步编程的核心技术与实践方案,涵盖从基础使用到高级优化的全链路知识。通过深入剖析 Async 注解原理、线程池配置策略、异步异常处理机制等关键技术点,结合典型业务场景的代码示例,…...
激光雷达YDLIDAR X2 SDK安装
激光雷达YDLIDAR X2 SDK安装 陈拓 2024/12/15-2024/12/19 1. 简介 YDLIDAR X2官方网址https://ydlidar.cn/index.htmlYDLIDAR X2 YDLIDAR X2是一款高性能的激光雷达传感器,具有以下主要特点和规格参数: 测距频率:3000Hz 扫描频…...
大模型在肝硬化风险预测及临床决策中的应用研究
目录 一、引言 1.1 研究背景与意义 1.2 研究目的与创新点 1.3 研究方法与数据来源 二、肝硬化及大模型相关理论基础 2.1 肝硬化概述 2.2 大模型技术原理 2.3 大模型在医疗领域的应用现状 三、大模型预测肝硬化术前风险 3.1 术前风险因素分析 3.2 大模型预测术前风险…...
计算机毕业设计SpringBoot+Vue.js母婴商城(源码+LW文档+PPT+讲解+开题报告)
温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 作者简介:Java领…...
Java多线程三:补充知识
精心整理了最新的面试资料,有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 Lambda表达式 简介: 希腊字母表中排序第十一位的字母,英语名称为Lambda避免匿名内部类定义过多其实质属于函数式编程的概念 为什么要使用lam…...
计算机网络————(一)HTTP讲解
基础内容分类 从TCP/IP协议栈为依托,由上至下、从应用层到基础设施介绍协议。 1.应用层: HTTP/1.1 Websocket HTTP/2.0 2.应用层的安全基础设施 LTS/SSL 3.传输层 TCP 4.网络层及数据链路层 IP层和以太网 HTTP协议 网络页面形成基本 流程:…...
stream流常用方法
1.reduce 在Java中,可以使用Stream API的reduce方法来计算一个整数列表的乘积。reduce方法是一种累积操作,它可以将流中的元素组合起来,返回单个结果。对于计算乘积,你需要提供一个初始值(通常是1,因为乘法…...
最新扣子(Coze)案例教程:全自动DeepSeek 写影评+批量生成 + 发布飞书,提效10 倍!手把手教学,完全免费教程
👨💻群里有同学是做影视赛道的博主,听说最近DeepSeek这么火,咨询能不能用DeepSeek写影评,并整理电影数据资料,自动发布到飞书文档,把每天的工作做成一个自动化的流程。 那今天斜杠君就为大家…...
数据结构:动态数组vector
vector 是 C 标准库的动态数组。 在C语言中一般初学者会使用malloc,int[n]等方式来创建静态数组,但是这种方式繁琐且容易出错。我们做算法题一般使用动态数组vector, 并且在刷题网站的题目给的输入一般也是vector类型。 示例:vect…...
【HeadFirst系列之HeadFirst设计模式】第9天之模板方法模式:从咖啡和茶到Spring框架,掌握设计模式的精髓
模板方法模式:从咖啡和茶到Spring框架,掌握设计模式的精髓 《Head First 设计模式》是一本经典的设计模式入门书籍,它以轻松幽默的方式讲解了设计模式的核心思想。其中,模板方法模式是一个非常简单但非常实用的设计模式ÿ…...
力扣hot100——排序链表(常见方法,归并排序)
解题思路: 分解(Divide):将待排序的列表递归地分成两半,直到每个子列表只包含一个元素(此时每个子列表都是有序的)。解决(Conquer):递归地对每个子列表进行排…...
使用 DeepSeek 和 ECharts 实现大屏数据可视化
引言 在当今数据驱动的时代,数据可视化成为了分析和展示数据的重要手段。大屏数据可视化不仅能够直观地展示数据,还能帮助决策者快速理解复杂信息。本文将介绍如何结合 DeepSeek(一个强大的数据处理与分析工具)和 ECharts(一个流行的数据可视化库)来实现大屏数据可视化。…...
基于springboot+vue的新生报到管理系统
一、系统架构 前端:vue | element-ui | echarts 后端:springboot | mybatis-plus | jwt 环境:jdk1.8 | mysql | maven 二、代码及数据 三、功能介绍 01. 登录 02. 首页 03. 管理员-系统管理-用户管理 04. 管理员-系统…...
【面试系列】Java开发--AI常见面试题
文章目录 1、实际工作或学习中用过哪些Ai工具1.1、AI编程1.2、AI对话聊天1.3、AI图像工具1.4、AI办公工具 2、谈谈你知道的AI领域的一些常见词汇及其含义的理解? 例如AIGC、LLM、DeepLearning分别是什么意思?2.1、AIGC(Artificial Intelligen…...
Maven 基础环境搭建与配置(二)
四、本地仓库配置,存储依赖 在 Maven 的世界里,本地仓库就像是一个 “私人储物间”,专门用来存放项目所需的各种依赖构件,如 JAR 包、WAR 包等。当我们构建项目时,Maven 会首先在本地仓库中查找所需的依赖,…...
了解ffmpeg,安装并配置环境变量
一、了解FFmpeg FFmpeg 是一个功能强大的开源多媒体框架,能够处理音视频的录制、转换和流媒体传输。它由 Fabrice Bellard 发起,采用 LGPL/GPL 许可证,广泛应用于各种平台,包括 Linux、Windows 和 macOS 什么是FFmpeg࿱…...
Deepseek reasoning-content 透出调研
Deepseek reasoning-content 透出调研 部署方式:Docker Ollama Deepseek-R1:8b 参考: https://help.apiyi.com/deepseek-reasoning-content-guide.htmlhttps://yuluo-yx.github.io/blog/%E4%BD%BF%E7%94%A8-Ollama-%E9%83%A8%E7%BD%B2-DeepSeek-%E5…...
终极免费音频转换器fre:ac:5分钟从新手到高手的完整指南 [特殊字符]
终极免费音频转换器fre:ac:5分钟从新手到高手的完整指南 🎯 【免费下载链接】freac The fre:ac audio converter project 项目地址: https://gitcode.com/gh_mirrors/fr/freac 你是否还在为音频格式不兼容而烦恼?想要把CD音乐转换成数…...
Stata实操:用xtreg命令搞定面板数据,固定效应和随机效应到底怎么选?
Stata面板数据分析实战:从数据清洗到模型选择的完整指南 当面对一份包含多个实体(如公司、国家或个人)在不同时间点观测值的数据集时,面板数据分析方法成为揭示深层规律的有力工具。不同于单纯的横截面或时间序列数据,…...
避开Verilog新手村陷阱:Hdlbits刷题时最容易犯的5个语法错误及调试技巧
避开Verilog新手村陷阱:Hdlbits刷题时最容易犯的5个语法错误及调试技巧 深夜的显示器前,你盯着Hdlbits的报错信息已经半小时——这已经是今晚第七次编译失败。Verilog语法看似简单,但那些隐藏在细节中的陷阱总能让初学者抓狂。本文将解剖五个…...
不止于安装:将PVE里的Win10打造成你的主力远程开发/测试环境(含性能调优与安全加固)
不止于安装:将PVE里的Win10打造成你的主力远程开发/测试环境(含性能调优与安全加固) 在虚拟化技术日益成熟的今天,Proxmox VE(PVE)作为开源的虚拟化平台,已经成为许多开发者和IT专业人士的首选。…...
RT-Thread BSP提交指南:从个人项目到社区贡献,你的代码如何通过审核并入主分支
RT-Thread BSP贡献全流程:从代码规范到社区合并的实战指南 当你完成了一个精心打磨的STM32 BSP开发后,如何让它从个人项目变成社区认可的官方资源?这份指南将带你深入理解RT-Thread社区的代码准入标准,避开常见陷阱,用…...
别再被‘HDR400’忽悠了!手把手教你读懂VESA DisplayHDR认证,买显示器不踩坑
别再被‘HDR400’忽悠了!手把手教你读懂VESA DisplayHDR认证,买显示器不踩坑 走进任何一家电子产品卖场或打开电商平台,显示器的宣传页上总能看到"HDR400"、"HDR600"这样的标签。这些看似专业的认证标识背后,…...
从超时到成功:深度解析并解决Hugging Face模型下载中的HTTPSConnectionPool与LocalEntryNotFoundError
1. 当模型下载变成一场噩梦:HTTPSConnectionPool与LocalEntryNotFoundError的真相 最近在处理PDF文档时,我遇到了一个让人抓狂的问题。当时我正在使用unstructured库的partition_pdf功能,系统突然抛出一连串红色错误提示:先是HTTP…...
从“神奇开关”到“智能控制”:用Arduino+双向可控硅(BTA16)DIY一个智能调光台灯
从“神奇开关”到“智能控制”:用Arduino双向可控硅(BTA16)DIY一个智能调光台灯 在智能家居日益普及的今天,调光功能已成为现代照明系统的标配。但对于电子爱好者和创客来说,亲手打造一个可调光台灯不仅能满足个性化需…...
AD7606多路采集时序翻车实录:从‘8+3路异常’到‘下降沿触发’的保姆级避坑指南
AD7606多路采集时序翻车实录:从‘83路异常’到‘下降沿触发’的保姆级避坑指南 当你在深夜的实验室里盯着示波器上那些跳动的波形,突然发现采集到的数据出现莫名其妙的错乱——前8路信号正常,后3路却像被施了魔法一样完全不对。这种场景对于使…...
【第3章>第11节】基于yolov2模型的人员打电话行为识别系统matlab仿真实现
本课程学习成功预览 目录 1.使用软件和版本 2.基于YOLOv2深度学习网络的人员打电话行为识别概述 3.yolov2模型的训练程序和测试程序matlab实现 4.基于YOLOv2深度学习网络的人员打电话行为识别仿真测试 5.程序讲解操作 欢迎订阅FPGA/MATLAB/Simulink系列教程 《★教程1:mat…...
