什么是定时器?

  前言👀~

上一章我们介绍了阻塞队列以及生产者消息模式,今天我们来讲讲定时器

定时器

标准库中的定时器

schedule()方法

扫描线程

手动实现定时器

任务类

存储任务的数据结构

定时器类


如果各位对文章的内容感兴趣的话,请点点小赞,关注一手不迷路,讲解的内容我会搭配我的理解用我自己的话去解释如果有什么问题的话,欢迎各位评论纠正 🤞🤞🤞

12b46cd836b7495695ce3560ea45749c.jpeg

个人主页:N_0050-CSDN博客

相关专栏:java SE_N_0050的博客-CSDN博客  java数据结构_N_0050的博客-CSDN博客  java EE_N_0050的博客-CSDN博客


定时器

定时器是个非常常见的组件,尤其是在网络进行通信的时候,类似发邮件,类似于一个 "闹钟",达到一个设定的时间之后, 就执行某个指定好的代码

举个例子,当客户端给服务器发送请求后,服务器半天没有响应,就像你发邮件一样,发的时候会转圈圈,成功了就会显示发送成功或者什么提示信息,如果服务器没有响应,你这边可能就一直在那转圈圈。我们也不知道是什么原因造成的,可能是请求没发过去,可能是响应丢了,也可能是服务器出现了问题。所以对于客户端来说,也可以说对用户来说,肯定不能一直等啊那体验多不好啊,所以设置一个等待时间(最大的期限),过了这个等待时间把电脑砸了,开个玩笑,过了这个最大期限,我们选择重新发一遍,或者直接不发,或者重开这个程序等等方式。这里的最大期限我们可以使用定时器去实现


标准库中的定时器

首先我们先使用一下定时器Timer类,再去讲解,代码如下

public class Test1 {public static void main(String[] args) {Timer timer = new Timer();timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("启动成功");}}, 1000);System.out.println("原神启动");}
}

输出结果


schedule()方法

这个方法涉及两个参数 第一个参数描述了任务要做什么这里使用匿名内部类去创建一个TimerTask实例第二个参数就是时间就是要在多长时间(单位为毫秒)后去执行,这个时间是根据当前时间为准然后根据你设定的时间来执行任务的,比如说现在11:00:00你设置1秒后执行就是11:00:01执行任务。然后前面用匿名内部类创建出来的TimerTask实例实现了Runnable接口,然后我们重写方法定义自己要执行的任务通过schedule方法,接着再由扫描线程去执行


扫描线程

当我们创建出这个timer对象后,这个线程也就被创建出来了,后续要执行任务,都是通过这个线程去执行的

来看刚才这段代码以及输出结果

public class Test1 {public static void main(String[] args) {Timer timer = new Timer();timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("启动成功");}}, 1000);System.out.println("原神启动");}
}

输出结果,你会发现整个进程并没有结束,主线程执行schedule方法的时候,是把这个任务丢给timer对象中的一个线程去处理的,这个线程可以叫"扫描线程",你设置的时间一到,就去扫描任务也就是执行你写的任务。

解释:为什么整个进程没有结束?timer中的这个线程阻止了进程结束,它在等我们再给它安排任务,相当于服务员,你有什么吩咐它就执行,没有任务就在那等并且timer里可以安排多个任务


手动实现定时器

根据上面标准库可以得出以下要求:

1.和上面标准库提供的timer类一样,我们需要一个扫描线程,然后去执行任务

2.需要一个数据结构,把所有要执行的任务保存起来

3.需要使用一个类,通过一个类的对象去来描述执行的任务(任务内容和执行时间)


任务类

首先写一个用来描述任务的类,包含任务内容和执行时间

在设置任务执行时间的时候,有两种方式,一种是相对的时间,一种是绝对的时间(完整的时间戳),两种都可以这里我们选择绝对时间,因为相对时间要计算间隔后的时间然后进行比较,绝对时间获取当前时间戳加上任务执行时间然后进行比较。

下面是任务类的实现,不只这一种,最后完整代码有两种

//用来描述任务的类 包含任务的内容和执行时间
class MyTimerTask {private Runnable runnable;private long time;public MyTimerTask(Runnable runnable, long delay) {this.runnable = runnable;this.time = System.currentTimeMillis() + delay;//使用绝对时机 时间戳+传入的时间}public Runnable getRunnable() {return runnable;}public long getTime() {return time;}
}


存储任务的数据结构

这里的数据结构我们采用优先级队列去保存需要执行的任务,因为我们肯定要先执行时间最少的任务,然后优先级队列也就是堆,最顶层的就是最小的,并且优先级队列取出元素(也就是获取时间最少的任务)时间复杂度都为O(1)

    public PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();

但是注意优先队列要求放入的元素是可以比较的,也就是我们的任务之间可以进行比较,所以我们还需要实现自定义比较器,使用时间进行比较除了优先级队列中的元素需要能进行比较的,还有二叉搜索树也就是TreeMap和TreeSet

    public PriorityQueue<MyTimerTask> queue = new PriorityQueue<>(new Comparator<MyTimerTask>() {@Overridepublic int compare(MyTimerTask o1, MyTimerTask o2) {return (int) (o1.getTime() - o2.getTime());}});

定时器类

我们的定时器和标准库中的定时器一样,我们需要一个扫描线程执行任务,还需要一个schedule方法,上面的优先级队列也放在定时器中,下面是代码实现,需要注意线程不安全问题,会出现这样的可能就比如主线程在向队列添加元素的时候,扫描线程也在对队列进行判断,导致加入了元素的时候这里正好进行判断,然后为空进入阻塞状态

class MyTimer {//优先级队列存储任务 优先级队列的元素要能进行比较 所以要实现比较器 我们根据时间进行比较public PriorityQueue<MyTimerTask> queue = new PriorityQueue<>(new Comparator<MyTimerTask>() {@Overridepublic int compare(MyTimerTask o1, MyTimerTask o2) {return (int) (o1.getTime() - o2.getTime());}});public MyTimer() {//创建出定时器对象的时候 启动扫描线程thread.start();}//给用户调用的方法 传入要完成的任务以及时间public void schedule(Runnable runnable, long delay) {synchronized (lock) {//避免线程不安全问题 有任务了就唤醒线程进行工作if (delay < 0) {throw new IllegalArgumentException("输入的时间有误");} else {queue.offer(new MyTimerTask(runnable, delay));//调用这个方法的时候 创建任务然后放进队列进行处理lock.notify();}}}public Object lock = new Object();public Thread thread = new Thread(() -> {synchronized (lock) {while (true) {//即使没任务 也等我们给它分配任务while (queue.isEmpty()) {//队列为空进入阻塞 使用while保险起见try {lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}long currentTime = System.currentTimeMillis();//记录当前时间MyTimerTask task = queue.peek();//先看任务的时间 如果到了再pollif (currentTime >= task.getTime()) {queue.poll();task.getRunnable().run();//获取到引用去执行用户的任务} else {}}}});
}

还有一个地方需要进行优化比如就是你设置执行任务的时间在10点半,然后else那块不写代码,它会一直到while循环开始判断一路下路,一直到时间到去执行任务,这样做消耗太多cpu资源,解决办法,让线程在这里休息,使用带参数的wait方法,当前执行任务时间减去当前时间作为参数

    public Thread thread = new Thread(() -> {synchronized (lock) {while (true) {//即使没任务 也等我们给它分配任务while (queue.isEmpty()) {//队列为空进入阻塞 使用while保险起见try {lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}long currentTime = System.currentTimeMillis();//记录当前时间MyTimerTask task = queue.peek();//先看任务的时间 如果到了再pollif (currentTime >= task.getTime()) {queue.poll();task.getRunnable().run();//获取到引用去执行用户的任务} else {}}}});

两种完整代码

第一种任务类是没有直接实现Runnable接口

//用来描述任务的类 包含任务的内容和执行时间
class MyTimerTask {private Runnable runnable;private long time;public MyTimerTask(Runnable runnable, long delay) {this.runnable = runnable;this.time = System.currentTimeMillis() + delay;//使用绝对时机 时间戳+传入的时间}public Runnable getRunnable() {return runnable;}public long getTime() {return time;}
}//定时器
class MyTimer {//优先级队列存储任务 优先级队列的元素要能进行比较 所以要实现比较器 我们根据时间进行比较public PriorityQueue<MyTimerTask> queue = new PriorityQueue<>(new Comparator<MyTimerTask>() {@Overridepublic int compare(MyTimerTask o1, MyTimerTask o2) {return (int) (o1.getTime() - o2.getTime());}});public MyTimer() {//创建出定时器对象的时候 启动扫描线程thread.start();}//给用户调用的方法 传入要完成的任务以及时间public void schedule(Runnable runnable, long delay) {synchronized (lock) {//避免线程不安全问题 有任务了就唤醒线程进行工作if (delay < 0) {throw new IllegalArgumentException("输入的时间有误");} else {queue.offer(new MyTimerTask(runnable, delay));//调用这个方法的时候 创建任务然后放进队列进行处理lock.notify();}}}public Object lock = new Object();public Thread thread = new Thread(() -> {synchronized (lock) {while (true) {//即使没任务 也等我们给它分配任务while (queue.isEmpty()) {//队列为空进入阻塞 使用while保险起见try {lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}long currentTime = System.currentTimeMillis();//记录当前时间MyTimerTask task = queue.peek();//先看任务的时间 如果到了再pollif (currentTime >= task.getTime()) {queue.poll();task.getRunnable().run();//获取到引用去执行用户的任务} else {}}}});
}

第二种是任务类实现Runnable接口

//用来描述任务的类 就是存储任务的内容以及执行时间
class MyTimerTask implements Runnable {private long time;private Runnable task;public MyTimerTask(Runnable runnable, long delay) {this.task = runnable;this.time = System.currentTimeMillis() + delay;//使用绝对时间 当前时间戳+多少秒后执行=执行时间}public long getTime() {return time;}@Overridepublic void run() {//外层的这个就是一个壳,通过调用这个方法执行里面我们自己写的任务task.run();// 这个就是我们自己写的任务}
}//定时器 包含存储队列 扫描线程 创建任务
class MyTimer {public Object lock = new Object();//使用优先级队列存储任务 因为取出任务的时间复杂度为0(1) 注意要比较器 因为我们要使用时间比较出谁是最小的public PriorityQueue<MyTimerTask> queue = new PriorityQueue<>(new Comparator<MyTimerTask>() {@Overridepublic int compare(MyTimerTask o1, MyTimerTask o2) {return (int) (o1.getTime() - o2.getTime());//return Long.compare(o1.getTime(), o2.getTime());//可以避免溢出}});//初始化定时器就启动扫描线程public MyTimer() {thread.start();}//把任务和执行时间传到这方法 然后通过这个方法创建任务类去装任务和时间public void schedule(Runnable runnable, long delay) {synchronized (lock) {if (delay < 0) {throw new IllegalArgumentException("输入的时间有误!!!");} else {queue.offer(new MyTimerTask(runnable, delay));lock.notify();}}}//创建扫描线程执行任务public Thread thread = new Thread(() -> {//因为扫描线程会一直扫描任务 它在等我们再给它安排任务while (true) {synchronized (lock) {while (queue.isEmpty()) {try {lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}//不是直接poll 任务执行的时候要和当前时间进行比较后 再进行poll去执行//这个拿的任务相当于我们自己写的任务MyTimerTask task = queue.peek();long currentTime = System.currentTimeMillis();//如果当前时间等于或者超过任务的执行时间就执行任务if (currentTime >= task.getTime()) {task.run();//queue.poll();} else {try {//让线程休息到执行任务的时间lock.wait(task.getTime() - currentTime);} catch (InterruptedException e) {e.printStackTrace();}}}}});
}

以上便是本章内容,定时器在日常开发中还是会用到的,例如发邮件这类的,所以还是需要好好掌握,我们下一章再见💕

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

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

相关文章

【Python】列表

目录 一、列表的概念 二、列表的创建 1.变量名 [ ] ..... 2.通过Python内置 的I ist类的构造函数来创建列表 三、操作列表元素的方法 1. 修改 2. 增加元素 3. 删除 4. 其他操作 四、遍历列表 五、列表排序 六、列表切片&#xff08;list slicing&#xff09; 七、…

值得细读的8个视觉大模型生成式预训练方法

作者&#xff1a;vasgaowei&#xff08;已授权原创&#xff09; 编辑: AI生成未来 链接&#xff1a;https://zhuanlan.zhihu.com/p/677794719 大语言模型的进展催生出了ChatGPT这样的应用&#xff0c;让大家对“第四次工业革命”和“AGI”的来临有了一些期待&#xff0c;也作为…

Linux基础指令介绍与详解——原理学习

前言&#xff1a;本节内容标题虽然为指令&#xff0c;但是并不只是讲指令&#xff0c; 更多的是和指令相关的一些原理性的东西。 如果友友只想要查一查某个指令的用法&#xff0c; 很抱歉&#xff0c; 本节不是那种带有字典性质的文章。但是如果友友是想要来学习的&#xff0c;…

深入浅出:npm常用命令详解与实践【保姆级教程】

大家好,我是CodeQi! 在我刚开始学习前端开发的时候,有一件事情让我特别头疼:管理和安装各种各样的依赖包。 那时候,我还不知道 npm 的存在,手动下载和管理这些库简直是噩梦。 后来,我终于接触到了 npm(Node Package Manager),它不仅帮我解决了依赖管理问题,还让我…

Python深度理解系列之【排序算法——冒泡排序】

读者大大们好呀&#xff01;&#xff01;!☀️☀️☀️ &#x1f440;期待大大的关注哦❗️❗️❗️ &#x1f680;欢迎收看我的主页文章➡️木道寻的主页 文章目录 &#x1f525;前言&#x1f680;冒泡排序python实现算法实现图形化算法展示 ⭐️⭐️⭐️总结 &#x1f525;前…

Apache POI、EasyPoi、EasyExcel

目录 ​编辑 &#xff08;一&#xff09;Apache PoI 使用 &#xff08;二&#xff09;EasyPoi使用 &#xff08;三&#xff09;EasyExcel使用 写 读 最简单的读​ 最简单的读的excel示例​ 最简单的读的对象​ &#xff08;一&#xff09;Apache PoI 使用 &#xff08;二&…

Git 安装

目录 Git 安装 Git 安装 在使用 Git 前我们需要先安装 Git。Git 目前支持 Linux/Unix、Solaris、Mac 和 Windows 平台上运行。Git 各平台安装包下载地址为&#xff1a;http://git-scm.com/downloads 在 Linux 平台上安装&#xff08;包管理工具安装&#xff09; 首先&#xff0…

IIS在Windows上的搭建

&#x1f4d1;打牌 &#xff1a; da pai ge的个人主页 &#x1f324;️个人专栏 &#xff1a; da pai ge的博客专栏 ☁️宝剑锋从磨砺出&#xff0c;梅花香自苦寒来 目录 一 概念&#xff1a; 二网络…

深入理解C++中的锁

目录 1.基本互斥锁&#xff08;std::mutex&#xff09; 2.递归互斥锁&#xff08;std::recursive_mutex&#xff09; 3.带超时机制的互斥锁&#xff08;std::timed_mutex&#xff09; 4.带超时机制的递归互斥锁&#xff08;std::recursive_timed_mutex&#xff09; 5.共享…

【python脚本】批量检测sql延时注入

文章目录 前言批量检测sql延时注入工作原理脚本演示 前言 SQL延时注入是一种在Web应用程序中利用SQL注入漏洞的技术&#xff0c;当传统的基于错误信息或数据回显的注入方法不可行时&#xff0c;例如当Web应用进行了安全配置&#xff0c;不显示任何错误信息或敏感数据时&#x…

【TS】TypeScript 原始数据类型深度解析

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 TypeScript 原始数据类型深度解析一、引言二、基础原始数据类型2.1 boolean2.2 …

苍穹外卖--sky-take-out(四)10-12

苍穹外卖--sky-take-out&#xff08;一&#xff09; 苍穹外卖--sky-take-out&#xff08;一&#xff09;-CSDN博客​编辑https://blog.csdn.net/kussm_/article/details/138614737?spm1001.2014.3001.5501https://blog.csdn.net/kussm_/article/details/138614737?spm1001.2…

Unity动画系统(2)

6.1 动画系统基础2-3_哔哩哔哩_bilibili p316 模型添加Animator组件 动画控制器 AnimatorController AnimatorController 可以通过代码控制动画速度 建立动画间的联系 bool值的设定 trigger p318 trigger点击的时候触发&#xff0c;如喊叫&#xff0c;开枪及换子弹等&#x…

错误 [WinError 10013] 以一种访问权限不允许的方式做了一个访问套接字的尝试 python ping

报错提示&#xff1a;错误 [WinError 10013] 以一种访问权限不允许的方式做了一个访问套接字的尝试 用python做了一个批量ping脚本&#xff0c;在windows专业版上没问题&#xff0c;但是到了windows服务器就出现这个报错 解决方法&#xff1a;右键 管理员身份运行 这个脚本 …

sql拉链表

1、定义&#xff1a;维护历史状态以及最新数据的一种表 2、使用场景 1、有一些表的数据量很大&#xff0c;比如一张用户表&#xff0c;大约1亿条记录&#xff0c;50个字段&#xff0c;这种表 2.表中的部分字段会被update更新操作&#xff0c;如用户联系方式&#xff0c;产品的…

在 WebGPU 与 Vulkan 之间做出正确的选择(Making the Right Choice between WebGPU vs Vulkan)

在 WebGPU 与 Vulkan 之间做出正确的选择&#xff08;Making the Right Choice between WebGPU vs Vulkan&#xff09; WebGPU 和 Vulkan 之间的主要区别WebGPU 是什么&#xff1f;它适合谁使用&#xff1f;Vulkan 是什么&#xff1f;它适合谁使用&#xff1f;WebGPU 和 Vulkan…

修改CentOS7 yum源

修改CentOS默认yum源为阿里镜像源 备份系统自带yum源配置文件 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup 下载ailiyun的yum源配置文件 CentOS7 yum源如下&#xff1a; wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun…

[OC]萝卜圈Python手动机器人脚本

这是给机器人设置的端口&#xff0c;对照用 代码 # #作者:溥哥’ ##机器人驱动主程序 #请在main中编写您自己的机器人驱动代码 import msvcrt def main():a"none"while True:key_input msvcrt.getch()akey_inputif abw:print(a)robot_drv.set_motors(1,40,2,40,3,…

uniapp学习笔记

uniapp官网地址&#xff1a;https://uniapp.dcloud.net.cn/ 学习源码&#xff1a;https://gitee.com/qingnian8/uniapp-ling_project.git 颜色网址&#xff1a;https://colordrop.io/ uniapp中如何获取导航中的路由信息&#xff1f; onLoad(e){console.log(e)console.log(e.w…

2.2.4 C#中显示控件BDPictureBox 的实现----ROI交互

2.2.4 C#中显示控件BDPictureBox 的实现----ROI交互 1 界面效果 在设定模式下&#xff0c;可以进行ROI 框的拖动&#xff0c;这里以Rect1举例说明 2 增加ROI类定义 /// <summary> /// ROI_single /// 用于描述图片感兴趣区域 /// type: 0:Rect1;1:Rect2;2:Circle ;3:…