【JavaEE精炼宝库】多线程(7)定时器

目录

一、定时器的概念

二、标准库中的定时器

三、自己实现一个定时器

3.1 MyTimerTask 实现:

3.2 MyTimer 实现:


一、定时器的概念

定时器也是软件开发中的⼀个重要组件。类似于一个 "闹钟"。达到一个设定的时间之后,就执行某个指定好的代码(可以用来完成线程池里面的非核心线程的超时回收)。

定时器是一种实际开发中非常常用的组件。 比如网络通信中,如果对方 500ms 内没有返回数据,则断开连接尝试重连。比如⼀个 Map,希望里面的某个 key 在 3s 之后过期(自动删除)。类似于这样的场景就需要用到定时器。

二、标准库中的定时器

标准库中提供了⼀个 Timer 类。Timer 类的核心方法为 schedule。

schedule 包含两个参数。第⼀个参数指定即将要执行的任务代码,第二个参数指定多长时间之后 执行(单位为毫秒)。如下:

其中第一个参数 TimerTask 是一个抽象类,本质上还是实现了 Runnable 接口,所以我们就可以把它当作 Runnable 来使用即可。 

• 使用演示:

public class Main {public static void main(String[] args) {Timer timer = new Timer();timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("hello 3000");}},3000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("hello 2000");}},2000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("hello 1000");}},1000);}
}

案例效果演示:

会间隔对应的时间打印。

三、自己实现一个定时器

我们在写代码之前要想好我们的需求是什么,也就是我们要实现什么,我们定时器的需求:1. 是能够延时执行任务 / 指定时间执行任务。2. 能够管理多个任务。

3.1 MyTimerTask 实现:

首先我们先实现任务,可以实现 Runnable 接口,或者采用把 Runnable 作为类参数,来进行实现。这里我们采用把 Runnable 作为类参数来进行实现。为了起到延时的效果,我们还需要一个 time 参数来保存绝对的时间。

为什么需要绝对时间呢?

答:这个其实很好理解,我们举一个栗子来解释:假如现在是早上 9 点,领导让你 1 小时之后去找他,也就是说我们应该在早上 10 点左右去找他,但是如果我们只是记录 1 个小时,那么随着时间的推移,我们不能够知道这个 1 小时之后,是哪个时间点的。当然我们可以采用倒计时的方法来实现,但是这样我们还要不停的维护,这个倒计时,倒不如直接记录绝对时间来的简单。

这里我们还需要实现 Comparable 接口,为什么还需要实现这个接口呢?

答:在 Timer 类中,任务不仅仅只有一个,且绝对时间大小与进入队列的顺序没有绝对关系,那么我们如何在队列中快速找到绝对时间最小的任务呢(如果绝对时间最小的任务都不满足执行时间,那么后面的任务绝对也不满足)?显然需要使用到优先级队列(小根堆)来存储任务,但是我们自定义的类不能比较,所以我们需要实现 Comparable 接口来重写 CompareTo 方法。

具体代码如下:

class MyTimerTask implements Comparable<MyTimerTask> {private long time;//绝对时间private Runnable runnable;//加上 private 体现封装性public MyTimerTask(Runnable runnable, long time) {this.runnable = runnable;this.time = time + System.currentTimeMillis();//绝对时间}public void run() {//方便后续调用runnable.run();}public long getTime(){return time;}//重写比较器,从小到大排序@Overridepublic int compareTo(MyTimerTask o) {return this.time >= o.time ? 1 : -1;}
}

3.2 MyTimer 实现:

这个就是我们要实现的定时器,通过上面在 MyTimerTask 的分析可知,我们这里需要优先级队列来辅助管理任务。同时还需要一个线程来不停的执行队列中的任务,并且还要提供一个 schedule 方法。所以总共要实现的东西有:

• 线程

• 优先级队列(小根堆)

• schedule 方法

• 保证线程安全(通过使用锁的方式要实现)

注意:这里我们不使用 Java 自带的优先级阻塞队列,原因是:优先级阻塞队列本身内部就有一个锁,我们为了保证线程安全,外面还要加一层锁,如果使用阻塞队列,那就是两个锁嵌套的情况,一不小心就会出现死锁的情况,所以倒不如我们同一处理,只使用一个锁即可。在自己实现阻塞队列的时候不能使用 continue 来循环等待(“忙等”),这样很消耗 CPU 资源,也不能使用 sleep 来进行阻塞,因为 sleep 不能释放锁(抱着锁睡),线程睡了就真的睡了,综上我们选择采用 wait 的方式来进行阻塞。

还有一些小细节在代码中都有标注释,这里就不再赘述了。 

具体代码如下:

• 大体框架:

线程一直不停的扫描队首元素,看看是否能执行这个任务。

class MyTimer {private Object locker = new Object();//不用阻塞优先级队列,因为有两个锁,一不小心就死锁了private PriorityQueue<MyTimerTask> heap = new PriorityQueue<MyTimerTask>();//因为有实现 comparable 所以 不用再传入比较器public MyTimer(){Thread thread = new Thread(() -> {try{while (true) {synchronized (locker) {if (heap.isEmpty()) {locker.wait();}MyTimerTask tmp = heap.peek();long curTime = System.currentTimeMillis();if (curTime >= tmp.getTime()) {//执行tmp.run();heap.poll();} else {//时间还未到locker.wait(tmp.getTime() - curTime);}}}}catch(InterruptedException e){//把异常统一处理throw new RuntimeException(e);}});thread.start();//线程启动}
}

• schedule 方法:

 Timer 类提供的核心方法为 schedule,用于注册一个任务,并指定这个任务多长时间后执行。这里加上锁有两个原因:1. 保证线程安全。2. 唤醒执行队列元素线程(如果在 wait 中的话)。

public void schedule(Runnable runnable,long delay){synchronized(locker){MyTimerTask task = new MyTimerTask(runnable,delay);heap.add(task);locker.notify();//这里必须要唤醒一下,因为添加新的任务后,绝对时间最小的不一定就是栈顶元素,要把新加入的元素一起考虑一下。}}

• 验证正确性:

还是上面在演示标准库 Timer 那里的案例。

public class demo1 {public static void main(String[] args) {MyTimer timer = new MyTimer();timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hello 3000");}},3000);timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hello 2000");}},2000);timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hello 1000");}},1000);}
}

案例演示效果如下:

可以看到符合我们预期的效果🎉🎉🎉

我们现在实现的是单线程的定时器,也可以实现多线程的定时器,只需要加个 List 来管理多个线程即可。

这里可能有的友友就有疑问:这样保证不了准时执行,在一个时间段,如果任务非常多的情况下。

其实无论如何都无法保证时间准的,只要你短时间内插入海量的任务,超过了 CPU 能够负担的极限都会不准,我们最多能做的就是把误差控制到一定的范围(加硬件)。

结语:

其实写博客不仅仅是为了教大家,同时这也有利于我巩固知识点,和做一个学习的总结,由于作者水平有限,对文章有任何问题还请指出,非常感谢。如果大家有所收获的话还请不要吝啬你们的点赞收藏和关注,这可以激励我写出更加优秀的文章。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/30707.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

聊一聊 Monitor.Wait 和 Pluse 的底层玩法

一&#xff1a;背景 1. 讲故事 在dump分析的过程中经常会看到很多线程卡在Monitor.Wait方法上&#xff0c;曾经也有不少人问我为什么用 !syncblk 看不到 Monitor.Wait 上的锁信息&#xff0c;刚好昨天有时间我就来研究一下。 二&#xff1a;Monitor.Wait 底层怎么玩的 1. 案…

Chromium 开发指南2024 Mac篇-Chromium项目编译小技巧(六)

1. 前言 在编译大型项目如 Chromium 时&#xff0c;优化编译速度是非常重要的。本文将介绍一些编译优化的小技巧&#xff0c;尤其是如何使用 ccache 来加速 C/C 代码的重新编译。ccache 是一个编译器缓存&#xff0c;通过缓存之前的编译并检测何时再次进行相同的编译&#xff…

“display interface“的43条信息,这条绝对被你忽略了

号主&#xff1a;老杨丨11年资深网络工程师&#xff0c;更多网工提升干货&#xff0c;请关注公众号&#xff1a;网络工程师俱乐部 大家好&#xff0c;我是张总。 上周和老杨唠嗑&#xff0c;他说我每次都是直播&#xff0c;或者视频号上给大家聊聊技术&#xff0c;都没发过技术…

U盘删除的文件怎么找回?数据恢复,5个方法

“我的u盘里有部分文件不小心被删除了&#xff0c;尝试了很多的方法都无法找回它们。U盘删除的文件怎么找回呢&#xff1f;希望大家给我出出主意&#xff01;” 保存了很多重要的文件在u盘中&#xff0c;查看u盘内存时却发现很多文件被删除了&#xff1f;别慌&#xff01;即使u…

B树(数据结构篇)

数据结构之B树 B-树(B-tree) 概念&#xff1a; B-树是一个非二叉树的多路平衡查找树(数据有序)&#xff0c;是一颗所有数据都存储在树叶节点上的树&#xff0c;不一定存储具体的数据&#xff0c;也可以是指向包含数据的记录的指针或地址 对于**阶为M(子节点数量在2和M之间)*…

Properties与xml知识点总结

文章目录 一、Properties1.1 构造方法1.2 从Properties文件中获取1.3 向Properties文件中存储 二、xml2.1 XML2.2 特点2.3 规则2.3 抬头声明2.4 特殊字符2.5 CDATA区段2.4 作用和应用场景 三、区别 一、Properties 定义&#xff1a;properties是一个双列集合集合&#xff0c;拥…

android 在线程中更新界面

在Android中&#xff0c;你不能直接从子线程中更新UI&#xff0c;因为这会导致应用崩溃。你需要使用Handler或runOnUiThread()来更新UI。 使用Handler 以下是如何使用Handler在子线程中更新UI的示例&#xff1a; 1. 创建Handler实例&#xff1a; import android.os.Bundle;…

产品经理方法论

1、用户体验 5 要素 1&#xff0c;表现层是你拿到一个产品以后&#xff0c;视觉表现&#xff0c;配色&#xff0c;布局&#xff0c;排版等等 2&#xff0c;框架层&#xff0c;是交互层面的东西&#xff0c;比如&#xff0c;操作情况&#xff0c;刷新&#xff0c;页面跳转&…

ChatmoneyAI如狂风般席卷广告创意舞台,轻松闯荡财富之海!

本文由 ChatMoney团队出品 引言 在广告创意行业&#xff0c;创新和高效是赢得市场的关键。而我今天要分享的就是如何利用ChatmoneyAI这款强大的人工智能工具&#xff0c;打破创新难题&#xff0c;赚取丰厚收益。 让我告诉你一个小秘密&#xff0c;有客户曾在一个月内&#xf…

git merge(3个模式) 与 git rebase 图文详解区别

目录 1 git merge1.1 模式一&#xff1a;fast-forward(–ff)1.2 模式二&#xff1a;non-Fast-forward(–no-ff)1.3 模式三&#xff1a;fast-forward only(–ff-only) 2 git rebase3 区别 1 git merge git merge有好几种不同的模式 默认情况下你直接使用 git merge 命令&#x…

从boost库到时间戳

一、以问题引入 授权证书一般有到期时间的说法&#xff0c;公司测试同事在测试更新后的证书时&#xff0c;将系统时间调到了2050年&#xff0c;重启服务后发现各个进程的cpu占用率特别高&#xff1b;结合日志分析&#xff0c;发现这些进程 都在不停的刷heartbeat()的日志&#…

C++17并行算法与HIPSTDPAR

C17 parallel algorithms and HIPSTDPAR — ROCm Blogs (amd.com) C17标准在原有的C标准库中引入了并行算法的概念。像std::transform这样的并行版本算法保持了与常规串行版本相同的签名&#xff0c;只是增加了一个额外的参数来指定使用的执行策略。这种灵活性使得已经使用C标准…

AI 音乐大模型:创新的曙光还是创意产业的阴影?

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

[面试题]Kafka

[面试题]Java【基础】[面试题]Java【虚拟机】[面试题]Java【并发】[面试题]Java【集合】[面试题]MySQL[面试题]Maven[面试题]Spring Boot[面试题]Spring Cloud[面试题]Spring MVC[面试题]Spring[面试题]MyBatis[面试题]Nginx[面试题]缓存[面试题]Redis[面试题]消息队列[面试题]…

RAG(检索增强生成)的演变:初级 RAG、高级 RAG 和模块化 RAG 架构

大型语言模型&#xff08;LLMs&#xff09;通过在自然语言任务及其它领域的成功应用&#xff0c;如 ChatGPT、Bard、Claude 等所示&#xff0c;已经彻底改变了 AI 领域。这些 LLMs 能够生成从创意写作到复杂代码的文本。然而&#xff0c;LLMs 面临着幻觉、过时知识和不透明、无…

基于ChatGPT的大型语言模型试用心得

近年来&#xff0c;ChatGPT这样的大型语言模型&#xff0c;它如同一颗冉冉升起的新星&#xff0c;迅速在商业、教育、娱乐等多个领域照亮了创新的天空&#xff0c;极大地革新了我们的工作与日常生活。 最近我发现一些国内用户也能自由访问的中文ChatGPT APP。这个平台不仅提供…

10招教你玩转Python循环优化

更多Python学习内容&#xff1a;ipengtao.com 在Python编程中&#xff0c;循环是最常见的控制结构之一。尽管Python的循环语法简单明了&#xff0c;但在处理大量数据或进行复杂计算时&#xff0c;循环可能会成为性能瓶颈。本文将介绍10种加速Python循环的方法&#xff0c;帮助在…

[Linux] 系统的基本架构特点

Linux系统的基本结构 Linux is also a subversion of UNIX,it follows the basic structure of UNIX 内核(kernel)&#xff1a; 操作系统的基本部分 管理与硬件相关的功能&#xff0c;分模块进行 常驻模块&#xff1a;进程控制IO操作文件\磁盘访问 用户不能直接访问内核 外壳(s…

数据资产:打破数据孤岛,实现数据互联互通,构建企业智能化转型的重要桥梁。通过高效整合与利用数据资源,推动企业决策的科学化、精准化,助力企业迈向智能化新时代

目录 一、引言 二、数据孤岛现象及其影响 三、打破数据孤岛&#xff0c;实现数据互联互通 1、制定统一的数据标准和管理规范 2、建设统一的数据平台 3、推广数据共享和开放文化 四、数据资产在智能化转型中的重要作用 1、推动企业决策的科学化、精准化 2、优化企业运营…

盘点下常见 HDFS JournalNode 异常的问题原因和修复方法

盘点下常见 HDFS JournalNode 异常的问题原因和修复方法 最近在多个客户现场以及公司内部环境&#xff0c;都遇到了因为 JournalNode 异常导致 HDFS 服务不可用的问题&#xff0c;在此总结下相关知识。 1 HDFS HA 高可用和 JournalNode 概述 HDFS namenode 有 SPOF 单点故障…