JavaEE之定时器及自我实现

ops/2025/1/12 13:47:28/

在生活当中,有很多事情,我们不是立马就去做,而是在规定了时间之后,在到该时间时,再去执行,比如:闹钟、定时关机等等,在程序的世界中,有些代码也不是立刻执行,那么我们该如何实现呢?一探究竟——>《定时器》

1. 定时器

定时器是什么

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

在Java当中也给我们提供了定时器(Timer)的类,请见下文。

标准库中的定时器

  • 标准库中提供了一个Timer类.Timer类的核心方法为schedule
  • schedule 包含两个参数.第⼀个参数指定即将要执行的任务代码,第⼆个参数指定多长时间之后执行(单位为毫秒).

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2. 自我实现一个定时器

1.首先定时器是用于处理任务的,我们该如何在定时器当中管理任务呢??

我们通过一个类,描述任务和任务执行的时间
具体任务的逻辑用Runnble表示,执行时间的可以用一个long型delay去表示

java">/*** 任务类*///由于需要比较时间大小,所以使用接口
class MyTask implements Comparable<MyTask>{//任务private Runnable runnable = null;//延迟时间private long time = 0;public MyTask(Runnable runnable, long delay) {//任务不能为空if(runnable==null){throw new RuntimeException("任务不能为空...");}//时间不能为负数if(delay<0){throw new RuntimeException("时间不能为负数...");}this.runnable = runnable;// 计算出任务执行的具体时间this.time = delay+System.currentTimeMillis();}public Runnable getRunnable() {return runnable;}public long getTime() {return time;}//比较当前任务和其他任务的时间@Overridepublic int compareTo(MyTask o) {return (int) (o.getTime()-this.getTime());}
}

2.通过MyTask描述了任务之后,由于任务的执行顺序不一样,我们该如何去管理任务呢?

我们通过一个优先级队列把任务的对象组织起来

java">//用阻塞队列来管理任务
private BlockingQueue<MyTask> queue=new PriorityBlockingQueue<>();

3.我们描述完了任务也通过优先级队列管理了任务对象,我们如何让任务对象和定时器关联起来呢?

java">    /*** 添加任务的方法* @param runnable 任务* @param delay 延时* @throws InterruptedException*/public void schedule(Runnable runnable, long delay) throws InterruptedException {// 根据传处的参数,构造一个MyTaskMyTask task=new MyTask(runnable,delay);// 把任务放入阻塞队列queue.put(task);}
}

4.我们通过schedule方法把任务对象添加到了阻塞队列当中,我们只需要创建一个线程来执行任务即可

此时我们的思路是:创建一个线程不停的扫描任务,取出队列的首元素若时间到就取出执行,时间没到就放回队列不执行,就能写出以下代码:

java">    // 创建扫描线程Thread thread=new Thread(()->{//不断的扫描队列中的任务while (true){try {//1.从阻塞队列中获取任务MyTask task = queue.take();//2.判断到没到执行时间long currentTime=System.currentTimeMillis();if(currentTime>=task.getTime()){//时间到了就执行任务task.getRunnable().run();}else {// 没有到时间,重新放回队列queue.put(task);}}catch (InterruptedException e) {e.printStackTrace();}}},"scanThread");//启动线程thread.start();      

但是上面的代码有一个很明显的问题,就是 “忙等” ,为什么呢?
在这里插入图片描述
那么我们怎么解决这个忙等这个问题呢?

在放回队列时让程序等待一段时间等待一段时间
时间为:下一个任务的执行时间和当前时间的差
那么既然要等待了我们必须要通过持有同一个锁,来完成等待操作,所以我们创建一把锁

修改代码如下:

java">// 创建扫描线程
Thread thread=new Thread(()->{//不断的扫描队列中的任务while (true){try {//1.从阻塞队列中获取任务MyTask task = queue.take();//2.判断到没到执行任务的时间long currentTime=System.currentTimeMillis();if(currentTime>=task.getTime()){//时间到了就执行任务task.getRunnable().run();}else {// 当前时间与任务执行时间的差long waitTime = task.getTime() - currentTime;// 没有到时间,重新放回队列queue.put(task);synchronized (locker){//等时间locker.wait(waitTime);}}}catch (InterruptedException e) {e.printStackTrace();}}},"scanThread");//启动线程,真正去系统中申请资源thread.start();

通过锁,解决了忙等问题,

5.此时还有一个新的问题,在该队列中若产生了新的任务执行时间在等待任务之前该怎么办呢?

我们在每一次向阻塞队列当中添加新任务时,我们就唤醒一次扫描线程即可

java">/*** 添加任务的方法* @param runnable 任务* @param delay 延时* @throws InterruptedException*/public void schedule(Runnable runnable, long delay) throws InterruptedException {// 根据传处的参数,构造一个MyTaskMyTask task=new MyTask(runnable,delay);// 把任务放入阻塞队列queue.put(task);//在每次添加新任务时,唤醒一次扫描线程,以访扫描线程还在等待,新任务时间过了的问题synchronized (locker){locker.notifyAll();}}
}

6.CPU调度的过程中可能会产生执行顺序的问题,或当一个线程执行到一半的时间被调度走的现象,会引发什么问题呢?

在这里插入图片描述

造成该现象的原因是没有保证原子性,我们扩大锁范围即可解决该问题,修改后的代码如下:

java">//不断的扫描队列中的任务while (true){synchronized (locker){try {//1.从阻塞队列中获取任务MyTask task = queue.take();//2.判断到没到执行任务的时间long currentTime=System.currentTimeMillis();if(currentTime>=task.getTime()){//时间到了就执行任务task.getRunnable().run();}else {// 当前时间与任务执行时间的差long waitTime = task.getTime() - currentTime;// 没有到时间,重新放回队列queue.put(task);locker.wait(waitTime);}}catch (InterruptedException e) {e.printStackTrace();}}}

7.由于进入锁之后,MyTask task = queue.take();操作,当阻塞队列中没有元素时,就会阻塞等待,直到队列中有可用元素才继续执行,但是由于MyTask task = queue.take();操作持有了锁,导致无法释放锁,添加任务的方法又迟迟取不到锁,导致一个在等着任务执行,一个在等着获取锁添加任务,造成了“死锁”现象,我们该如何解决呢?

我们发现在为了解决原子性问题时,我们扩大加锁的范围,却又引入了更大的问题
一般我们两害相全取其轻

为了解决无法及时执行任务的问题,我们创建了一个后台的扫描线程,只做定时唤醒操作,定时1s或者任意时间唤醒执行一次


完整的定时器实现代码如下:
java">import java.util.concurrent.BlockingQueue;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.TimeUnit;/*** 自我实现定时器*/
public class MyTimer {//用阻塞队列来管理任务private BlockingQueue<MyTask> queue=new PriorityBlockingQueue<>();//创建⼀个锁对象private Object locker = new Object();public MyTimer() throws InterruptedException {// 创建扫描线程Thread thread=new Thread(()->{//不断的扫描队列中的任务while (true){synchronized (locker){try {//1.从阻塞队列中获取任务MyTask task = queue.take();//2.判断到没到执行任务的时间long currentTime=System.currentTimeMillis();if(currentTime>=task.getTime()){//时间到了就执行任务task.getRunnable().run();}else {// 当前时间与任务执行时间的差long waitTime = task.getTime() - currentTime;// 没有到时间,重新放回队列queue.put(task);locker.wait(waitTime);}}catch (InterruptedException e) {e.printStackTrace();}}}},"scanThread");//启动线程,真正去系统中申请资源thread.start();//创建一个后台线程Thread daemonThread= new Thread(()->{while (true){//定时唤醒synchronized (locker){locker.notifyAll();}//休眠一会try {TimeUnit.MICROSECONDS.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}});//设置成后台线程daemonThread.setDaemon(true);//启动线程daemonThread.start();}/*** 添加任务的方法* @param runnable 任务* @param delay 延时* @throws InterruptedException*/public void schedule(Runnable runnable, long delay) throws InterruptedException {// 根据传处的参数,构造一个MyTaskMyTask task=new MyTask(runnable,delay);// 把任务放入阻塞队列queue.put(task);synchronized (locker){locker.notifyAll();}}
}/*** 任务类*/
class MyTask implements Comparable<MyTask>{//任务private Runnable runnable = null;//延迟时间private long time = 0;public MyTask(Runnable runnable, long delay) {//任务不能为空if(runnable==null){throw new RuntimeException("任务不能为空...");}//时间不能为负数if(delay<0){throw new RuntimeException("时间不能为负数...");}this.runnable = runnable;// 计算出任务执行的具体时间this.time = delay+System.currentTimeMillis();}public Runnable getRunnable() {return runnable;}public long getTime() {return time;}@Overridepublic int compareTo(MyTask o) {return (int) (o.getTime()-this.getTime());}
}

http://www.ppmy.cn/ops/149466.html

相关文章

Linux文件系统的安全保障---Overlayroot!

overlayroot 是一种使用 OverlayFS 实现的功能&#xff0c;可将根文件系统挂载为只读&#xff0c;并通过一个临时的写层实现对文件系统的修改。这种方法非常适合嵌入式设备或需要保持系统文件完整性和安全性的场景。下文以 RK3568 平台为例&#xff0c;介绍制作 overlayroot 的…

python学opencv|读取图像(三十一)缩放图像的三种方法

【1】引言 前序学习进程中&#xff0c;我们至少掌握了两种方法&#xff0c;可以实现对图像实现缩放。 第一种方法是调用cv2.resize()函数实现&#xff0c;相关学习链接为&#xff1a; python学opencv|读取图像&#xff08;三&#xff09;放大和缩小图像_python opencv 读取图…

Jaeger UI使用、采集应用API排除特定路径

Jaeger使用 注&#xff1a; Jaeger服务端版本为&#xff1a;jaegertracing/all-in-one-1.6.0 OpenTracing版本为&#xff1a;0.33.0&#xff0c;最后一个版本&#xff0c;停留在May 06, 2019。最好升级到OpenTelemetry。 Jaeger客户端版本为&#xff1a;jaeger-client-1.3.2。…

国产编辑器EverEdit - 扩展脚本:在当前文件目录下新建同类型文件

1 扩展脚本&#xff1a;在当前文件目录下新建同类型文件 1.1 应用场景 用户在进行编程语言学习时&#xff0c;比如&#xff1a;Python&#xff0c;经常做完一个小练习后&#xff0c;又需要新建一个文件&#xff0c;在新建文件的时候&#xff0c;不但要选择文件类型&#xff0c…

请求方式(基于注解实现)

1.编写web.xml文件配置启动信息 <!DOCTYPE web-app PUBLIC"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN""http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app><display-name>Archetype Created Web Application</di…

计算机网络——网络层-IP地址

一、IP 地址及其表示方法 • 我们把整个因特网看成为一个单一的、抽象的网络。IP 地址就是给每个连接在因特网上的主机&#xff08;或路由器&#xff09;分配一个在全世界范围是唯一的 32 位的标识符。 • IP 地址现在由因特网名字与号码指派公司ICANN (Internet Corporation f…

2025广州国际汽车内外饰技术展览会:引领汽车内外饰发展新潮流-Automotive Interiors

随着科技的不断进步和消费者对汽车品质的要求日益提高&#xff0c;汽车内外饰的设计和制造也在不断创新和发展。AUTO TECH China 2025广州国际汽车内外饰技术展览会作为行业内的重要盛会&#xff0c;将于2025年11月20日至22日在广州保利世贸博览馆盛大举办。本次展览会将汇集全…

系统架构设计师考点—信息安全和网络安全

一、备考指南 信息安全和网络安全主要考查的是信息安全属性、加密解密数字摘要、数字签名、PKI体系等相关知识&#xff0c;同时也是重点考点&#xff0c;在系统架构设计师的考试中一般会考选择题&#xff0c;占2~4分&#xff0c;在案例分析和论文中有时也会考到&#xff0c;属于…