【JavaEE初阶】 定时器详解与实现
文章目录
- 🌴定时器是什么
- 🎋Java标准库中的定时器
- 🌲模拟实现定时器
- 🚩定时器的构成
- 📌第一步:MyStack类的建立
- 📌第二步:创建MyTimer类
- 📌第三步:解决相关问题
- 🌳完整代码实现与测试
- ⭕总结
🌴定时器是什么
定时器也是软件开发中的一个重要组件. 类似于一个 “闹钟”. 达到一个设定的时间之后, 就执行某个指定好的代码
定时器是一种实际开发中非常常用的组件.
比如网络通信中, 如果对方 500ms 内没有返回数据, 则断开连接尝试重连.
比如一个 Map, 希望里面的某个 key 在 3s 之后过期(自动删除).
类似于这样的场景就需要用到定时器.
🎋Java标准库中的定时器
-
标准库中提供了一个 Timer 类. Timer 类的核心方法为 schedule .
-
schedule 包含两个参数.
-
第一个参数指定即将要执行的任务代码,
-
第二个参数指定多长时间之后执行 (单位为毫秒)
代码示例:
下面程序分别有一个定时器,设置了三个不同的时间
import java.util.Timer;
import java.util.TimerTask;public class TestDemo {public static void main(String[] args) {Timer timer = new Timer();System.out.println("程序启动!");timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("定时器3");}},3000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("定时器2");}},2000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("定时器1");}},1000);}
}
运行结果如下:
结果如我们所示,按照时间顺序进行打印
🌲模拟实现定时器
首先我们先来看一下定时器的构成
🚩定时器的构成
- 一个带优先级的阻塞队列
- 为啥要带优先级呢?
因为阻塞队列中的任务都有各自的执行时刻 (delay). 最先执行的任务一定是 delay 最小的. 使用带优先级的队列就可以高效的把这个 delay 最小的任务找出来.
-
队列中的每个元素是一个 MyTask 对象.
-
MyTask 中带有一个时间属性, 队首元素就是即将要执行的任务
-
同时有一个线程一直扫描队首元素, 看队首元素是否需要执行
📌第一步:MyStack类的建立
包含两个属性
-
包含一个 Runnable 对象
-
一个 time(毫秒时间戳)
由于我们的MyTask类需要放入一个带优先级的阻塞队列中,所以我们需要MyTack可以比较,这里博主选择重写 Comparable 接口里的compareTo方法
代码实现如下:
public class MyTask implements Comparable<MyTask> {private Runnable runnable;private long time;public MyTask() {System.out.println(1);}public void tad() {System.out.println(2);}public MyTask(Runnable runnable, long time) {this.runnable = runnable;this.time = time;}public long gettime(MyTask) {return this.time;}//执行任务public void run() {runnable.run();}@Overridepublic int compareTo(MyTask o) {return (int)(this.time - o.time);}
}
📌第二步:创建MyTimer类
该类需要有一个带有优先级的阻塞队列
还需要有一个可schedule 方法用于我们来插入我们我们需要执行的任务
public class MyTimer {// 有一个阻塞优先级队列, 来保存任务.private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();// 指定两个参数// 第一个参数是 任务 内容// 第二个参数是 任务 在多少毫秒之后执行. 形如 1000public void schedule(Runnable runnable, long after) {// 注意这里的时间上的换算MyTask task = new MyTask(runnable, System.currentTimeMillis() + after);queue.put(task);}
}
-
由于我们输入的都是一个时间大小,所以我们需要进行处理一下,
-
这里的System.currentTimeMillis()是获取当时的时间戳
-
再加上所需要的时间大小即达到我们效果
其次还需要一个线程循环扫描
-
该扫描我们需要做的是
-
取出队首元素, 检查看看队首元素任务是否到时间了.
-
如果时间没到, 就把任务塞回队列里去.
-
如果时间到了, 就把任务进行执行.
private Thread t = null;public MyTimer() {t = new Thread() {@Overridepublic void run() {while (true) {try {// 取出队首元素, 检查看看队首元素任务是否到时间了.// 如果时间没到, 就把任务塞回队列里去.// 如果时间到了, 就把任务进行执行.MyTask myTask = queue.take();long curTime = System.currentTimeMillis();if (curTime < myTask.getTime()) {// 还没到点, 先不必执行// 现在是 13:00, 取出来的任务是 14:00 执行//塞回去queue.put(myTask);} else {// 时间到了!! 执行任务!!myTask.run();}} catch (InterruptedException e) {e.printStackTrace();}}}};//启动线程t.start();}
📌第三步:解决相关问题
- 问题一:while (true) 转的太快了, 造成了无意义的 CPU 浪费.
比如第一个任务设定的是 1 min 之后执行某个逻辑. 但是这里的 while (true) 会导致每秒钟访问队首元素几万次.
解决办法:引入一个locker对象, 借助该对象的 wait / notify 来解决 while (true) 的忙等问题.
我们在循环扫描里:引入 wait, 等待一定的时间.并修改 MyTimer 的 schedule 方法, 每次有新任务到来的时候唤醒一下循环扫描线程. (因为新插入的任务可能是需要马上执行的)
- 问题二:原子性问题
由于我们的出队列操作和判断语句不具有原子性
问题情况如下:
出队列操作拿到任务后,还没有进行判断
然后这时候有一个来了一个新任务
但是此时我们该任务还没有wait()操作,而且我们由于添加新元素,notify()操作已执行,这就导致后面的wait操作不会被唤醒,那么新来的任务就在相应时间来没有被执行
解决方法:将出队列操作与判断操作都加上锁
代码实现如下:
import java.util.concurrent.PriorityBlockingQueue;public class MyTimer {// 有一个阻塞优先级队列, 来保存任务.private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();// 扫描线程private Thread t = null;private Object locker = new Object();public MyTimer() {t = new Thread() {@Overridepublic void run() {while (true) {try {// 取出队首元素, 检查看看队首元素任务是否到时间了.// 如果时间没到, 就把任务塞回队列里去.// 如果时间到了, 就把任务进行执行.synchronized (locker) {MyTask myTask = queue.take();long curTime = System.currentTimeMillis();if (curTime < myTask.getTime()) {// 还没到点, 先不必执行// 现在是 13:00, 取出来的任务是 14:00 执行queue.put(myTask);// 在 put 之后, 进行一个 waitlocker.wait(myTask.getTime() - curTime);} else {// 时间到了!! 执行任务!!myTask.run();}}} catch (InterruptedException e) {e.printStackTrace();}}}};t.start();}// 指定两个参数// 第一个参数是 任务 内容// 第二个参数是 任务 在多少毫秒之后执行. 形如 1000public void schedule(Runnable runnable, long after) {// 注意这里的时间上的换算MyTask task = new MyTask(runnable, System.currentTimeMillis() + after);queue.put(task);synchronized (locker) {locker.notify();}}
}
🌳完整代码实现与测试
计时器完整代码:
import java.util.concurrent.PriorityBlockingQueue;class MyTask implements Comparable<MyTask> {private Runnable runnable;private long time;public MyTask() {System.out.println(1);}public void tad() {System.out.println(2);}public MyTask(Runnable runnable, long time) {this.runnable = runnable;this.time = time;}public long getTime() {return this.time;}//执行任务public void run() {runnable.run();}@Overridepublic int compareTo(MyTask o) {return (int)(this.time - o.time);}
}public class MyTimer {// 有一个阻塞优先级队列, 来保存任务.private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();// 扫描线程private Thread t = null;private Object locker = new Object();public MyTimer() {t = new Thread() {@Overridepublic void run() {while (true) {try {// 取出队首元素, 检查看看队首元素任务是否到时间了.// 如果时间没到, 就把任务塞回队列里去.// 如果时间到了, 就把任务进行执行.synchronized (locker) {MyTask myTask = queue.take();long curTime = System.currentTimeMillis();if (curTime < myTask.getTime()) {// 还没到点, 先不必执行// 现在是 13:00, 取出来的任务是 14:00 执行queue.put(myTask);// 在 put 之后, 进行一个 waitlocker.wait(myTask.getTime() - curTime);} else {// 时间到了!! 执行任务!!myTask.run();}}} catch (InterruptedException e) {e.printStackTrace();}}}};t.start();}// 指定两个参数// 第一个参数是 任务 内容// 第二个参数是 任务 在多少毫秒之后执行. 形如 1000public void schedule(Runnable runnable, long after) {// 注意这里的时间上的换算MyTask task = new MyTask(runnable, System.currentTimeMillis() + after);queue.put(task);synchronized (locker) {locker.notify();}}
}
测试代码如下
public class TestDemo2 {public static void main(String[] args) {MyTimer myTimer = new MyTimer();System.out.println("程序启动");myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("计时器3");}},3000);myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("计时器2");}},2000);myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("计时器1");}},1000);}
}
测试结果如下:
⭕总结
关于《【JavaEE初阶】 定时器详解与实现》就讲解到这儿,感谢大家的支持,欢迎各位留言交流以及批评指正,如果文章对您有帮助或者觉得作者写的还不错可以点一下关注,点赞,收藏支持一下!
相关文章:

【JavaEE初阶】 定时器详解与实现
文章目录 🌴定时器是什么🎋Java标准库中的定时器🌲模拟实现定时器🚩定时器的构成📌第一步:MyStack类的建立📌第二步:创建MyTimer类📌第三步:解决相关问题 &am…...

基于YOLOv8模型和WiderPerson数据集的行人目标检测系统(PyTorch+Pyside6+YOLOv8模型)
摘要:基于YOLOv8模型和WiderPerson数据集的行人目标检测系统可用于日常生活中检测与定位行人目标,利用深度学习算法可实现图片、视频、摄像头等方式的目标检测,另外本系统还支持图片、视频等格式的结果可视化与结果导出。本系统采用YOLOv8目标…...

COSCon'23 开源社文创丨 给开源人一点“color see see”
成都城市限定 “小O在成都”行李箱贴纸 成都限定行李箱贴纸把小O和特色元素相融合 当小O遇到成都 在云端漫步的蓝色小章鱼 掉落到这座热情似火的城市, 结识了大熊猫朋友 学会了四川麻将 吃到了红油串串... 快带着小O来一场自由的旅游吧! “你也要尝尝竹子…...

C++前缀和算法的应用:从仓库到码头运输箱子原理、源码、测试用例
本文涉及的基础知识点 C算法:前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频 双指针 单调双向队列 题目 你有一辆货运卡车,你需要用这一辆车把一些箱子从仓库运送到码头。这辆卡车每次运输有 箱子数目的限制 和 总重量的限制 。 给你…...

【面试HOT100】链表树
系列综述: 💞目的:本系列是个人整理为了秋招面试的,整理期间苛求每个知识点,平衡理解简易度与深入程度。 🥰来源:材料主要源于LeetCodeHot100进行的,每个知识点的修正和深入主要参考…...

了解 Elasticsearch 自动生成的文档 _id:重复是一个问题吗?
Elasticsearch 中自动生成的文档 ID 当你在未指定 ID 的情况下对文档建立索引时,Elasticsearch 会自动为该文档生成唯一的 ID。 该 ID 是 Base64 编码的 UUID,由多个部分组成,每个部分都有特定的用途。 ID 生成过程针对索引速度和存储效率进…...
量子信息处理器可能能够提供高度压缩的生成对抗学习任务的版本
量子信息处理在生成对抗学习任务中的应用可能性,以及量子信息处理器在表示高维向量和执行线性代数运算上的优势。 举个例子 假设底层数据由M个在N维实数或复数空间中的归一化向量~vj组成,使得数据的(归一化)协方差矩阵为C (1/M…...

linux-守护进程daemon
linux-守护进程daemon 代码实现 main.c运行结果 代码实现 main.c //pName:程序名 //facility: 守护进程,输出日志类型 302页 #include<signal.h> #include<syslog.h> #include<fcntl.h> static int daemon_proc 0; #defin…...

Kafka Tool(Kafka 可视化工具)安装及使用教程
Kafka Tool(Kafka 可视化工具)安装及使用教程 Kafka Tool 工具下载 下载地址 http://www.kafkatool.com/download.html 下载界面 不同版本的Kafka对应不同版本的工具,个人使用的是2.11,所以下载的是最新的2.0.8版本ÿ…...

【大揭秘】美团面试题:ConcurrentHashMap和Hashtable有什么区别?一文解析!
正文 亲爱的小伙伴们,大家好!我是小米,一个热爱技术分享的程序员,今天我为大家带来了一篇有关美团面试题的热门话题:ConcurrentHashMap 和 Hashtable 有什么区别。这个问题在Java面试中常常被拿来考察对多线程编程的理…...

爬虫基础 JS逆向
爬虫核心 1. HTTP协议与WEB开发 1. 什么是请求头请求体,响应头响应体 2. URL地址包括什么 3. get请求和post请求到底是什么 4. Content-Type是什么 (1)简介 HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)…...
nextTick实现原理
答题思路: 此题实际考查vue异步更新策略说出vue是怎么通过异步、批量的方式更新以提高性能的最后把源码中实现说一下 回答范例: vue有个批量、异步更新策略,数据变化时,vue开启一个队列,并缓冲在同一事件循环中发生的…...
CentOS 7中安装ZooKeeper
文章目录 下载解压安装环境变量配置文件启动设置开机自启动开放端口 CentOS 7.6 ZooKeeper 3.5.7 本文介绍了如何在CentOS 7系统中安装单机版的ZooKeeper。 下载 点击官网下载 解压安装 # 解压 tar -xzvf apache-zookeeper-3.5.7-bin.tar.gz sudo mv apache-zookeeper-3.5.…...

推荐《幽游白书》
《幽游白书》是日本漫画家富坚义博于1990年12月3日—1994年7月25日于集英社旗下杂志《周刊少年Jump》上连载的少年漫画作品,全175话(含外传一话)。现时发行的单行本共计19册,电子版由漫番漫画、哔哩哔哩漫画发布 [1-2] 。 本作最…...

Linux MMC子系统 - 1.eMMC简介
By: Ailson Jack Date: 2023.10.21 个人博客:http://www.only2fire.com/ 本文在我博客的地址是:http://www.only2fire.com/archives/160.html,排版更好,便于学习,也可以去我博客逛逛,兴许有你想要的内容呢。…...

聊聊Android线程优化这件事
一、背景 在日常开发APP的过程中,难免需要使用第二方库和第三方库来帮助开发者快速实现一些功能,提高开发效率。但是,这些库也可能会给线程带来一定的压力,主要表现在以下几个方面: 线程数量增多:一些库可…...

Linux性能优化--实用工具:性能工具助手
8.0 概述 本章介绍一些在Linux系统上可用的实用程序,它们能够加强性能工具的有效性和可用性。实用工具本身不是性能工具,但是当它们与性能工具一起使用时,它们可以帮助完成如下功能:自动执行繁琐的任务、分析性能统计数据&#x…...

[PyTorch]即插即用的热力图生成
先上张效果图,本来打算移植霹雳老师的使用Pytorch实现Grad-CAM并绘制热力图。但是看了下代码,需要骨干网络按照标准写法(即将特征层封装为features数组),而我写的网络图省事并没有进行封装,改造网络的代价又…...
golang笔记18--go并发多线程
golang笔记18--go并发多线程 介绍核心用法MutexRWMutexWaitGroupCondOncemapPoolContextselect 注意事项参考文档 介绍 大家都知道go语言近年来越来越火了,其中有一个要点是go语言在并发场景有很高的性能,比如可以通过启动很多个 goroutine 来执行并发任…...
使用OkHttp和Java来下载
以下是一个使用OkHttp和Java来下载内容的下载器程序,同时使用了jshk.com.cn/get_proxy来获取代理服务器。请注意,为了简化代码,我们将忽略一些异常处理和安全性检查。 import java.io.File;import java.io.FileOutputStream;import java.io.I…...
Java 语言特性(面试系列1)
一、面向对象编程 1. 封装(Encapsulation) 定义:将数据(属性)和操作数据的方法绑定在一起,通过访问控制符(private、protected、public)隐藏内部实现细节。示例: public …...

【OSG学习笔记】Day 18: 碰撞检测与物理交互
物理引擎(Physics Engine) 物理引擎 是一种通过计算机模拟物理规律(如力学、碰撞、重力、流体动力学等)的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互,广泛应用于 游戏开发、动画制作、虚…...

基于Flask实现的医疗保险欺诈识别监测模型
基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施,由雇主和个人按一定比例缴纳保险费,建立社会医疗保险基金,支付雇员医疗费用的一种医疗保险制度, 它是促进社会文明和进步的…...
2024年赣州旅游投资集团社会招聘笔试真
2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...
基于数字孪生的水厂可视化平台建设:架构与实践
分享大纲: 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年,数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段,基于数字孪生的水厂可视化平台的…...

在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…...

佰力博科技与您探讨热释电测量的几种方法
热释电的测量主要涉及热释电系数的测定,这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中,积分电荷法最为常用,其原理是通过测量在电容器上积累的热释电电荷,从而确定热释电系数…...

安宝特案例丨Vuzix AR智能眼镜集成专业软件,助力卢森堡医院药房转型,赢得辉瑞创新奖
在Vuzix M400 AR智能眼镜的助力下,卢森堡罗伯特舒曼医院(the Robert Schuman Hospitals, HRS)凭借在无菌制剂生产流程中引入增强现实技术(AR)创新项目,荣获了2024年6月7日由卢森堡医院药剂师协会࿰…...
NPOI操作EXCEL文件 ——CAD C# 二次开发
缺点:dll.版本容易加载错误。CAD加载插件时,没有加载所有类库。插件运行过程中用到某个类库,会从CAD的安装目录找,找不到就报错了。 【方案2】让CAD在加载过程中把类库加载到内存 【方案3】是发现缺少了哪个库,就用插件程序加载进…...