模拟实现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…...
系统设计 --- MongoDB亿级数据查询优化策略
系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log,共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题,不能使用ELK只能使用…...
什么是库存周转?如何用进销存系统提高库存周转率?
你可能听说过这样一句话: “利润不是赚出来的,是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业,很多企业看着销售不错,账上却没钱、利润也不见了,一翻库存才发现: 一堆卖不动的旧货…...
在WSL2的Ubuntu镜像中安装Docker
Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包: for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...
Go语言多线程问题
打印零与奇偶数(leetcode 1116) 方法1:使用互斥锁和条件变量 package mainimport ("fmt""sync" )type ZeroEvenOdd struct {n intzeroMutex sync.MutexevenMutex sync.MutexoddMutex sync.Mutexcurrent int…...
嵌入式常见 CPU 架构
架构类型架构厂商芯片厂商典型芯片特点与应用场景PICRISC (8/16 位)MicrochipMicrochipPIC16F877A、PIC18F4550简化指令集,单周期执行;低功耗、CIP 独立外设;用于家电、小电机控制、安防面板等嵌入式场景8051CISC (8 位)Intel(原始…...
加密通信 + 行为分析:运营商行业安全防御体系重构
在数字经济蓬勃发展的时代,运营商作为信息通信网络的核心枢纽,承载着海量用户数据与关键业务传输,其安全防御体系的可靠性直接关乎国家安全、社会稳定与企业发展。随着网络攻击手段的不断升级,传统安全防护体系逐渐暴露出局限性&a…...
用递归算法解锁「子集」问题 —— LeetCode 78题解析
文章目录 一、题目介绍二、递归思路详解:从决策树开始理解三、解法一:二叉决策树 DFS四、解法二:组合式回溯写法(推荐)五、解法对比 递归算法是编程中一种非常强大且常见的思想,它能够优雅地解决很多复杂的…...
数据库——redis
一、Redis 介绍 1. 概述 Redis(Remote Dictionary Server)是一个开源的、高性能的内存键值数据库系统,具有以下核心特点: 内存存储架构:数据主要存储在内存中,提供微秒级的读写响应 多数据结构支持&…...
写一个shell脚本,把局域网内,把能ping通的IP和不能ping通的IP分类,并保存到两个文本文件里
写一个shell脚本,把局域网内,把能ping通的IP和不能ping通的IP分类,并保存到两个文本文件里 脚本1 #!/bin/bash #定义变量 ip10.1.1 #循环去ping主机的IP for ((i1;i<10;i)) doping -c1 $ip.$i &>/dev/null[ $? -eq 0 ] &&am…...
PydanticAI快速入门示例
参考链接:https://ai.pydantic.dev/#why-use-pydanticai 示例代码 from pydantic_ai import Agent from pydantic_ai.models.openai import OpenAIModel from pydantic_ai.providers.openai import OpenAIProvider# 配置使用阿里云通义千问模型 model OpenAIMode…...
