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

[定时器]

目录

一. 定时器的使用

二. 定时器的实现


我们的日常生活中离不开闹钟, 代码中同样需要闹钟, 代码中的"闹钟"就叫做"定时器". 在java标准库中, 也提供了定时器的实现.

一. 定时器的使用

import java.util.Timer;
import java.util.TimerTask;public class Demo31 {public static void main(String[] args) {Timer timer = new Timer(); //创建一个Timer类的对象timer.schedule(new TimerTask() {  // schedule(安排), 指定参数两个TimerTask和delay. TimerTask表示要执行的任务, delay表示在多长时间之后执行该任务@Overridepublic void run() {System.out.println("hello world");}}, 3000);System.out.println("程序开始运行");}
}

java中 用Timer类中schedule来完成定时器的实现. schedule()需要指定两个参数:

(1) TimerTask, 这个参数表示了程序要执行的任务.

(2) delay (延时时间), 这个参数表示程序在多长时间之后执行该任务.

上面代码就实现了一个人非常简单的定时器, 这段代码表示: 先打印"程序开始运行", 等待3000ms之后再执行TimerTask中的任务 (打印"hello world") . 

Timer也可以安排多个任务:

import java.util.Timer;
import java.util.TimerTask;public class Demo31 {public static void main(String[] args) {Timer timer = new Timer(); //创建一个Timer类的对象timer.schedule(new TimerTask() {// schedule(安排), 指定参数两个TimerTask和delay. TimerTask表示要执行的任务, delay表示在多长时间之后执行该任务@Overridepublic void run() {System.out.println("hello world 3");}}, 3000);timer.schedule(new TimerTask() {  // Timer也可以安排多个任务@Overridepublic void run() {System.out.println("hello world 2");}}, 2000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("hello world 1");}}, 1000);System.out.println("程序开始运行");}
}

 上述代码, 由于hello1 延时1000ms打印, hello2延时2000ms打印, hello3延时3000ms打印, 所以打印顺序应该是hello1 -> hello2 -> hello3 

二. 定时器的实现

了解了定时器的基本用法之后, 接下来我们就自己具体实现一下定时器吧~

实现一个定时器, 需要完成以下几个重要的步骤:

(1) 创建类, 描述一个要执行的任务 (任务的内容, 任务执行的时间).

(2) 管理多个任务: 通过一定的数据结构, 把多个任务管理起来.

(3) 指定专门的线程, 执行这里的任务.

import java.util.PriorityQueue;class MyTimerTask implements Comparable<MyTimerTask>{private Runnable runnable;private long time;public MyTimerTask(Runnable runnable, long delay) {  //runnable->要执行的任务, delay->延时时间this.runnable = runnable;this.time = System.currentTimeMillis() + delay;// System.currentTimeMillis() --> 当前的时间戳.}public void run() {runnable.run();}public long getTime() {return time;}@Overridepublic int compareTo(MyTimerTask o) {//此处减的顺序就决定了是大堆还是小堆 (我们这里需要小堆)// 谁减谁才能得到小堆? -> 实验(尝试)的方式return (int) (this.time - o.time);}
}
class MyTimer {//private List<MyTimerTask> list = new ArrayList<>();// 这里使用list保存任务不是一个很好的选择. 如果用list保存任务,// 后续我们执行列表中的任务的时候, 就需要依次遍历每个元素, 任务执行完毕之后, 还需要把对应的任务从list中删除掉.private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();// 此时这里的TimerTask还需要指定比较规则.(实现comparable接口 / 指定comparator)public MyTimer() {// 创建线程, 执行上述队列中的内容.Thread t = new Thread(() -> {while (true) {if (queue.isEmpty()) { //如果队列为空, 无法取出任务. continuecontinue;}MyTimerTask current = queue.peek(); //取出队首元素if (System.currentTimeMillis() >= current.getTime()) {//执行任务current.run();//删除执行过的任务queue.poll();} else {//时间没到, 不执行任务continue;}}});t.start();}public void schedule(Runnable runnable, long delay) {MyTimerTask myTimerTask = new MyTimerTask(runnable, delay);queue.offer(myTimerTask);}}

显然, 上述代码没有针对线程安全问题作出处理.

import java.util.PriorityQueue;class MyTimerTask implements Comparable<MyTimerTask>{private Runnable runnable;private long time;public MyTimerTask(Runnable runnable, long delay) {  /this.runnable = runnable;this.time = System.currentTimeMillis() + delay;// System.currentTimeMillis() --> 当前的时间戳.}public void run() {runnable.run();}public long getTime() {return time;}@Overridepublic int compareTo(MyTimerTask o) {return (int) (this.time - o.time);}
}
class MyTimer {private Object locker = new Object();public MyTimer() {// 创建线程, 执行上述队列中的内容.Thread t = new Thread(() -> {while (true) {synchronized (locker) {if (queue.isEmpty()) { //如果队列为空, 无法取出任务. continuecontinue;}MyTimerTask current = queue.peek(); //取出队首元素if (System.currentTimeMillis() >= current.getTime()) {//执行任务current.run();//删除执行过的任务queue.poll();} else {//时间没到, 不执行任务continue;}}}});t.start();}public void schedule(Runnable runnable, long delay) {synchronized (locker) {MyTimerTask myTimerTask = new MyTimerTask(runnable, delay);queue.offer(myTimerTask);}}
}

当初始情况下,队列为空时, 经过 if 判定, 就会执行continue, 在执行while循环, 再if判定, 还是空, 再continue, 这样的话, 当前线程就一直在循环做一些没有意义的事情, 而且还占用着锁资源.  所以, 我们这里执行wait()是一个更好的选择. 在这里wait(), 就需要在queue加入元素之后唤醒(notify). 我们在前一篇文章阻塞队列那里是说过, wait()外的循环使用while更好一些, 所以我们这里把if换成while.

假设队列中已经包含元素了, 但是执行时间还没到, 那么 if 判定不满足, 就会执行else, 执行里面的continue, 然后在while(true)进来, 再执行到 if 判定, 仍不满足, 再执行else, 继续continue, 这样一来, 这里就跟上面的那种情况一样了, 在循环做一些没有意义的事情, 而且占用着锁资源. 那么如何解决这样的问题呢? --> 还是使用wait(), 不过这里wait()不用notify唤醒, 而是使用超时时间. 等待了指定时间之后自动唤醒.  注意这里使用wait()而不能使用sleep(), 因为如果在sleep过程中来了一个执行时间更早的任务, 那么sleep不会唤醒, 但是wait()就会被唤醒. 而且sleep在休眠的时候不会释放锁资源, 它是"抱着锁"睡的, 这样的话其他线程想拿锁资源就无法拿到了.

这里我们不使用java提供的BlockingQueue而是自己加锁的原因是: 如果使用BlockingQueue, 那么它的take()方法和wait()带锁, 这里就出现两把锁了, 就很有可能导致死锁问题的出现.

import java.util.PriorityQueue;class MyTimerTask implements Comparable<MyTimerTask>{private Runnable runnable;private long time;public MyTimerTask(Runnable runnable, long delay) {  this.runnable = runnable;this.time = System.currentTimeMillis() + delay;// System.currentTimeMillis() --> 当前的时间戳.}public void run() {runnable.run();}public long getTime() {return time;}@Overridepublic int compareTo(MyTimerTask o) {return (int) (this.time - o.time);}
}
class MyTimer {private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();// 此时这里的TimerTask还需要指定比较规则.(实现comparable接口 / 指定comparator)private Object locker = new Object();public MyTimer() {// 创建线程, 执行上述队列中的内容.Thread t = new Thread(() -> {try{while (true) {synchronized (locker) {while (queue.isEmpty()) { //如果队列为空, 无法取出任务. continue//continue;locker.wait();}MyTimerTask current = queue.peek(); //取出队首元素if (System.currentTimeMillis() >= current.getTime()) {//执行任务current.run();//删除执行过的任务queue.poll();} else {//时间没到, 不执行任务//continue;locker.wait(current.getTime() - System.currentTimeMillis());// 用任务执行时间减去当前时间, 得到一个时间差, wait就等待这么长时间. 如果到点了就唤醒.}}}}catch (InterruptedException e) {throw new RuntimeException(e);}});t.start();}public void schedule(Runnable runnable, long delay) {synchronized (locker) {MyTimerTask myTimerTask = new MyTimerTask(runnable, delay);queue.offer(myTimerTask);locker.notify();}}
}

那么以上就是修改完全之后的最终代码了~

我们可以在main方法中试一下看它是否和系统提供的定时器功能一样:

public class Demo32 {public static void main(String[] args) {MyTimer myTimer = new MyTimer();myTimer.schedule(new Runnable() { // 可以重写run方法指定任务@Overridepublic void run() {System.out.println("hello 3000");}}, 3000);myTimer.schedule(() -> { //也可以使用lambda表达式System.out.println("hello 2000");},2000);myTimer.schedule(()-> {System.out.println("hello 1000");}, 1000);}
}

执行结果没有问题~ 

定时器除了用优先级队列的方式实现, 还有一种经典的实现方式 -> 时间轮. 这里我们就再不做讨论了.


http://www.mrgr.cn/news/71000.html

相关文章:

  • 用MVVM设计模式提升WPF开发体验:分层架构与绑定实例解析
  • 【css】html里面的图片宽度设为百分比,高度要与宽度一样
  • 【系统设计】理解带宽延迟积(BDP)、吞吐量、延时(RTT)与TCP发送窗口的关系:优化网络性能的关键
  • P8680 [蓝桥杯 2019 省 B] 特别数的和
  • 【Windows】CMD命令学习——系统命令
  • C#中break和continue的区别?
  • AI Agent智能数字员工解决案例
  • 关于第二台及其的 Anaconda的安装信息
  • 计算机组成原理之SISD,SIMD,MIMD,向量处理器的基本概念
  • 基于SpringBoot的“原创歌曲分享平台”的设计与实现(源码+数据库+文档+PPT)
  • 发布rust crate
  • 国际化视野下的新蓝海:如何参与海外短剧项目?
  • C语言结构体数组
  • 灾难恢复和业务连续性:制定有效的灾难恢复计划
  • Docker入门系列——Docker-Compose
  • 抓住鸿蒙生态崛起的机遇:开发者如何应对挑战,创造更好的应用体验
  • 怎么看真假国企啊?怎么识别假冒国企的千层套路?
  • string------1
  • 通过EtherNetIP转Profinet网关实现跨品牌EthernetIP协议的PLC通讯
  • 模型再训练软件环境部署说明
  • Python100道面试题(2024持续更新中............)
  • 【C++类型转换和IO流】
  • 丹摩征文活动 | Kolors入门:从安装到全面活用的对比指南
  • 数值优化 | 图解牛顿法、阻尼牛顿法与高斯牛顿法(附案例分析与Python实现)
  • Linux 实例:/etc/fstab 配置错误导致无法登录
  • MBTI关于考完PMP的碎碎念