当前位置: 首页 > news >正文

什么是定时器?

  前言👀~

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

定时器

标准库中的定时器

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();}}}}});
}

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

相关文章:

  • nginx优化和防盗链
  • 使用目标检测模型YOLO V10 OBB进行旋转目标的检测:训练自己的数据集(基于卫星和无人机的农业大棚数据集)
  • MySQL 教程
  • 短视频矩阵系统:打造品牌影响力的新方式
  • 什么是Web3D交互展示?有什么优势?
  • 关于 VuePress 的插件
  • MySQL 9.0 悄悄上线,支持面向AI的向量数据库
  • go语言怎么获取文件的大小并且转化为kb为单位呢?
  • 前端项目vue3/React使用pako库解压缩后端返回gzip数据
  • Rust单元测试、集成测试
  • Redis八股
  • 从这五部分入手可以完成一份出色的英文论文
  • 谈谈JVM内存区域的划分,哪些区域可能发生OutOfMemoryError?(jvm)
  • Vitis IDE 艰难切换--从传统 Vitis GUI 到 2024.1 统一软件界面
  • 【探索Linux】P.36(传输层 —— TCP协议段格式)
  • Android 初级面试者拾遗(前台界面篇)之 Activity 和 Fragment
  • Angular 响应式表单 基础例子
  • FineReport中如何实现自动滚屏效果
  • hadoop入门学习教程--DKHadoop完整安装步骤
  • IndexedDB
  • iOS | NSProxy
  • java2019面试题北京
  • jdbc就是这么简单
  • leetcode-27. Remove Element
  • LintCode 31. partitionArray 数组划分
  • Linux编程学习笔记 | Linux多线程学习[2] - 线程的同步
  • Linux各目录及每个目录的详细介绍
  • node.js
  • Shadow DOM 内部构造及如何构建独立组件
  • Xmanager 远程桌面 CentOS 7
  • 包装类对象
  • 和 || 运算
  • 记一次用 NodeJs 实现模拟登录的思路
  • 买一台 iPhone X,还是创建一家未来的独角兽?
  • 面试题:给你个id,去拿到name,多叉树遍历
  • 名企6年Java程序员的工作总结,写给在迷茫中的你!
  • 使用阿里云发布分布式网站,开发时候应该注意什么?
  • 使用前端开发工具包WijmoJS - 创建自定义DropDownTree控件(包含源代码)
  • 手机端车牌号码键盘的vue组件
  • 用jquery写贪吃蛇
  • 自制字幕遮挡器
  • #define MODIFY_REG(REG, CLEARMASK, SETMASK)
  • (14)学习笔记:动手深度学习(Pytorch神经网络基础)
  • (2024,RWKV-5/6,RNN,矩阵值注意力状态,数据依赖线性插值,LoRA,多语言分词器)Eagle 和 Finch
  • (翻译)Entity Framework技巧系列之七 - Tip 26 – 28
  • (附源码)ssm基于微信小程序的疫苗管理系统 毕业设计 092354
  • (含react-draggable库以及相关BUG如何解决)固定在左上方某盒子内(如按钮)添加可拖动功能,使用react hook语法实现
  • (四)Android布局类型(线性布局LinearLayout)
  • (四)c52学习之旅-流水LED灯
  • (五)IO流之ByteArrayInput/OutputStream
  • (转)Linux下编译安装log4cxx
  • (转)MVC3 类型“System.Web.Mvc.ModelClientValidationRule”同时存在
  • (转载)虚幻引擎3--【UnrealScript教程】章节一:20.location和rotation
  • * CIL library *(* CIL module *) : error LNK2005: _DllMain@12 already defined in mfcs120u.lib(dllmodu
  • . Flume面试题