模拟实现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 内没有返回数据, 则断开连接尝试重…...
Ubuntu 的RabbitMQ安装
目录 1.安装Erlang 查看erlang版本 退出命令 2. 安装 RabbitMQ 3.确认安装结果 4.安装RabbitMQ管理界面 5.启动服务并访问 1.启动服务 2.查看服务状态 3.通过IP:port 访问界面 4.添加管理员用户 a)添加用户名:admin,密码࿱…...
设计模式教程:命令模式(Command Pattern)
1. 什么是命令模式? 命令模式(Command Pattern)是一种行为型设计模式。它将请求封装成一个对象,从而使你能够用不同的请求、队列和日志请求以及支持可撤销操作。 简单来说,命令模式通过把请求封装成对象的方式解耦了…...
JavaScript数组常用的方法有哪些?map、filter、reduce 的区别和使用场景是什么?
JavaScript数组常用的方法有哪些?map、filter、reduce 的区别和使用场景是什么? JavaScript 数组常用方法 JavaScript 数组有很多实用的方法,以下先简单介绍一些常见的基础方法,再重点讲解 map、filter、reduce 这三个高阶函数。…...
vim修改只读文件
现象 解决方案 对于有root权限的用户,在命令行输入 :wq! 即可强制保存退出...
【DeepSeek】本地部署,保姆级教程
deepseek网站链接传送门:DeepSeek 在这里主要介绍DeepSeek的两种部署方法,一种是调用API,一种是本地部署。 一、API调用 1.进入网址Cherry Studio - 全能的AI助手选择立即下载 2.安装时位置建议放在其他盘,不要放c盘 3.进入软件后…...
为AI聊天工具添加一个知识系统 之114 详细设计之55 知识表征
本文要点 要点 项目名称:为使用AI聊天工具的聊天者添加一个知识系统 项目背景: 在现在各种AI聊天工具层出不穷的今天,我觉得特别需要一个通用的AI聊天工具的图形界面能够为每个聊天者(或一个利益相关者组织)建立自…...
centos 9 时间同步服务
在 CentOS 9 中,默认的时间同步服务是 chrony,而不是传统的 ntpd。 因此,建议使用 chrony 来配置和管理时间同步。 以下是使用 chrony 配置 NTP 服务的步骤: 1. 安装 chrony 首先,确保系统已安装 chrony。 在 CentOS…...
NCRE证书构成:全国计算机等级考试证书体系详解
全国计算机等级考试(NCRE)证书体系为中学生提供了一个系统学习和提升计算机能力的平台。本文将详细介绍 NCRE 证书的构成,帮助中学生了解 NCRE 证书的级别和内容,规划未来职业发展。 一、NCRE 证书体系概述 NCRE 证书共分为四个级…...
嵌入式之总线
嵌入式系统中的总线(Bus)是指用于连接各种组件(如处理器、存储器、外设等)的通信通道。总线的设计和实现对嵌入式系统的性能、功耗和扩展性有着重要影响。下面详细介绍嵌入式系统中的总线的概念、类型和特点。 一、总线的基本概念 总线是一种共享的通信路径,允许多个设备…...
如何在WPS打开的word、excel文件中,使用AI?
1、百度搜索:Office AI官方下载 或者直接打开网址:https://www.office-ai.cn/static/introductions/officeai/smartdownload.html 打开后会直接提示开始下载中,下载完成后会让其选择下载存放位置: 选择位置,然后命名文…...
Java 使用websocket
添加依赖 <!-- WebSocket 支持 --> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId> </dependency>添加配置类 Configuration public class WebSocketConfig {B…...
MySQL 视图入门
一、什么是 MySQL 视图 1.1 视图的基本概念 在 MySQL 中,视图是一种虚拟表,它本身并不存储实际的数据,而是基于一个或多个真实表(基表)的查询结果集。可以把视图想象成是一个预定义好的查询语句的快捷方式。当你查询…...
【设计模式】 代理模式(静态代理、动态代理{JDK动态代理、JDK动态代理与CGLIB动态代理的区别})
代理模式 代理模式是一种结构型设计模式,它提供了一种替代访问的方法,即通过代理对象来间接访问目标对象。代理模式可以在不改变原始类代码的情况下,增加额外的功能,如权限控制、日志记录等。 静态代理 静态代理是指创建的或特…...
高考或者单招考试需要考物理这科目
问题:帮忙搜索一下以上学校哪些高考或者单招考试需要考物理这科目的 回答: 根据目前获取的资料,明确提及高考或单招考试需考物理的学校为湖南工业职业技术学院,在部分专业单招时要求选考物理;其他学校暂未发现明确提…...
《A++ 敏捷开发》- 16 评审与结对编程
客户:我们的客户以银行为主,他们很注重质量,所以一直很注重评审。他们对需求评审、代码走查等也很赞同,也能找到缺陷,对提升质量有作用。但他们最困惑的是通过设计评审很难发现缺陷。 我:你听说过敏捷的结对…...
NutUI内网离线部署
文章目录 官网拉取源代码到本地仓库修改源代码打包构建nginx反向代理部署访问内网离线地址 在网上找了一圈没有写NutUI内网离线部署的文档,花了1天时间研究下,终于解决了。 对于有在内网离线使用的小伙伴就可以参考使用了 如果还是不会联系UP主:QQ:10927…...
【实战篇】【深度介绍 DeepSeek R1 本地/私有化部署大模型常见问题及解决方案】
引言 大家好!今天我们来聊聊 DeepSeek R1 的本地/私有化部署大模型。如果你正在考虑或者已经开始了这个项目,那么这篇文章就是为你准备的。我们会详细探讨常见问题及其解决方案,帮助你更好地理解和解决在部署过程中可能遇到的挑战。准备好了…...
数据结构--双向链表,双向循环链表
双向链表的头插,尾插,头删,尾删 头文件:(head.h) #include <string.h> #include <stdlib.h> typedef…...
Qt学习(六) 软件启动界面 ,注册表使用 ,QT绘图, 视图和窗口绘图,Graphics View绘图框架:简易CAD
一 软件启动界面 注册表使用 知识点1:这样创建的界面是不可以拖动的,需要手动创建函数来进行拖动,以下的3个函数是从父类继承过来的函数 virtual void mousePressEvent(QMouseEvent *event);virtual void mouseReleaseEvent(QMouseEvent *eve…...
java练习(36)
ps:题目来自力扣 给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 . 和 * 的正则表达式匹配。 . 匹配任意单个字符* 匹配零个或多个前面的那一个元素 所谓匹配,是要涵盖 整个 字符串 s 的,而不是部分字符串。 class Solution {publ…...
go 网络编程 websocket gorilla/websocket
在 Go 语言中,你可以使用标准库中的 net/http 包和第三方库 gorilla/websocket 来实现一个 WebSocket 服务器。gorilla/websocket 库提供了对 WebSocket 协议的高级抽象,使得处理 WebSocket 连接变得相对简单。 package mainimport ("fmt"&qu…...
deepseek与其他大模型配合组合
DeepSeek与其他大模型的配合组合,展现了其在多个领域中的强大应用潜力和灵活性。以下是对DeepSeek与其他大模型配合组合的详细分析: 一、DeepSeek与华知大模型的组合 背景介绍: 华知大模型是同方知网与华为联手打造的,具备全学科…...
【Linux网络编程】IP协议格式,解包步骤
目录 解析步骤 1.版本字段(大小:4比特位) 2.首部长度(大小:4比特位)(单位:4字节) 🍜细节解释: 3.服务类型(大小:8比特…...
Unity 位图字体
下载Bitmap Font Generator BMFont - AngelCode.com 解压后不用安装直接双击使用 提前设置 1、设置Bit depth为32 Options->Export options 2、清空所选字符 因为我们将在后边导入需要的字符。 Edit->Select all chars 先选择所有字符 Edit->Clear all chars i…...
Linux 网络与常用操作(适合开发/运维/网络工程师)
目录 OSI 七层协议简介 应用层 传输层 Linux 命令!!! 1. ifconfig 命令 简介 1. 查看网络地址信息 2. 指定开启、或者关闭网卡 3. 修改、设置 IP 地址 4. 修改机器的 MAC 地址信息 5. 永久修改网络设备信息 2. route 路由命令 …...
指定定网卡名称
一、PCIe网卡名称指定 原理:利用udev规则匹配PCIe设备的硬件特征(如总线位置、MAC地址等),覆盖默认命名规则 4 。 步骤: 获取设备信息: Bash udevadm info -a -p /sys/class/net/<原设备名> # 如e…...
linux 安装启动zookeeper全过程及遇到的坑
1、下载安装zookeeper 参考文章:https://blog.csdn.net/weixin_48887095/article/details/132397448 2、启动失败 1、启动失败JAVA_HOME is not set and java could not be found in PATH 已安装 JAVA 配置了JAVA_HOME,还是报错解决方法:参考…...
RD-搭建测试环境
测试团队职责 环境验证:确保开发部署的测试环境可访问,页面/接口无阻塞问题; 配置检查**:核对数据库连接、接口域名、HT证书等关键配置; 数据准备**:导入基线数据,隔离测试与生产数据࿱…...
MySQL数据库——表的约束
1.空属性(null/not null) 两个值:null(默认的)和not null(不为空) 数据库默认字段基本都是字段为空,但是实际开发时,尽可能保证字段不为空,因为数据为空没办法…...
