JavaEE之定时器及自我实现

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

1. 定时器

定时器是什么

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

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

标准库中的定时器

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

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

2. 自我实现一个定时器

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

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

/*** 任务类*///由于需要比较时间大小,所以使用接口
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描述了任务之后,由于任务的执行顺序不一样,我们该如何去管理任务呢?

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

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

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

    /*** 添加任务的方法* @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方法把任务对象添加到了阻塞队列当中,我们只需要创建一个线程来执行任务即可

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

    // 创建扫描线程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();      

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

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

修改代码如下:

// 创建扫描线程
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.此时还有一个新的问题,在该队列中若产生了新的任务执行时间在等待任务之前该怎么办呢?

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

/*** 添加任务的方法* @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调度的过程中可能会产生执行顺序的问题,或当一个线程执行到一半的时间被调度走的现象,会引发什么问题呢?

在这里插入图片描述

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

//不断的扫描队列中的任务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或者任意时间唤醒执行一次


完整的定时器实现代码如下:
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.mzph.cn/diannao/66817.shtml

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

相关文章

国产3D CAD将逐步取代国外软件

在工业软件的关键领域&#xff0c;计算机辅助设计&#xff08;CAD&#xff09;软件对于制造业的重要性不言而喻。近年来&#xff0c;国产 CAD 的发展态势迅猛&#xff0c;展现出巨大的潜力与机遇&#xff0c;正逐步改变着 CAD 市场长期由国外软件主导的格局。 国产CAD发展现状 …

linux:文件的创建/删除/复制/移动/查看/查找/权限/类型/压缩/打包

关于文件的关键词 创建 touch 删除 rm 复制 cp 权限 chmod 移动 mv 查看内容 cat(全部); head(前10行); tail(末尾10行); more,less 查找 find 压缩 gzip ; bzip 打包 tar 编辑 sed 创建文件 格式&#xff1a; touch 文件名 删除文件 复制文件 移动文件 查看文…

数据结构C语言描述11(图文结合)--二叉搜索树(BST树)的实现(数据采用KV存储形式进行封装)

前言 这个专栏将会用纯C实现常用的数据结构和简单的算法&#xff1b;有C基础即可跟着学习&#xff0c;代码均可运行&#xff1b;准备考研的也可跟着写&#xff0c;个人感觉&#xff0c;如果时间充裕&#xff0c;手写一遍比看书、刷题管用很多&#xff0c;这也是本人采用纯C语言…

Chrome_60.0.3112.113_x64 单文件版 下载

单文件&#xff0c;免安装&#xff0c;直接用~ Google Chrome, 免費下載. Google Chrome 60.0.3112.113: Chrome 是 Google 開發的網路瀏覽器。它的特點是速度快,功能多。 下载地址: https://blog.s3.sh.cn/thread-150-1-1.htmlhttps://blog.s3.sh.cn/thread-150-1-1.html

概率论与数理统计总复习

复习课本&#xff1a;中科大使用的教辅《概率论和数理统计》缪柏其、张伟平版本 目录 0.部分积分公式 1.容斥原理 2.条件概率 3.全概率公式 4.贝叶斯公式 5.独立性 6.伯努利分布&#xff08;两点分布&#xff09; 7.二项分布 8.帕斯卡分布&#xff08;负二项分布&am…

【线性代数】通俗理解特征向量与特征值

这一块在线性代数中属于重点且较难理解的内容&#xff0c;下面仅个人学习过程中的体会&#xff0c;错误之处欢迎指出&#xff0c;有更简洁易懂的理解方式也欢迎留言学习。 文章目录 概念计算几何直观理解意义PS.适用 概念 矩阵本身就是一个线性变换&#xff0c;对一个空间中的…

IDEA中创建maven项目

1. IDEA中创建maven项目 在IDEA中创建Maven项目&#xff0c;前提是已经安装配置好Maven环境。如还未配置安装Maven的&#xff0c;请先下载安装。如何下载安装&#xff0c;可参考我另外篇文章&#xff1a;maven的下载与安装教程本篇教程是以创建基于servlet的JavaWeb项目为例子&…

windows记事本不显示下划线

问题已解决 问题复现 解决方法 原因特定情况下默认的字体大小会导致下划线不可见&#xff0c;只需crtl加号的快捷键或者ctrl前或者后滚动鼠标滚轮以改变字体大小即可

git问题

拉取项目代码后&#xff0c;出现 1、找回未commit的代码 2、记录不全&#xff0c;只是显示部分代码记录

Operation001-Install

操作001&#xff1a;RabbitMQ安装 一、安装 # 拉取镜像 docker pull rabbitmq:3.13-management# -d 参数&#xff1a;后台运行 Docker 容器 # --name 参数&#xff1a;设置容器名称 # -p 参数&#xff1a;映射端口号&#xff0c;格式是“宿主机端口号:容器内端口号”。5672供…

rom定制系列------小米max3安卓12 miui14批量线刷 默认开启usb功能选项 插电自启等

小米Max3是小米公司于2018年7月19日发布的机型。此机型后在没有max新型号。采用全金属一体机身设计&#xff0c;配备6.9英寸全面屏.八核处理器骁龙636&#xff0c;后置双摄像头1200万500万像素&#xff0c;前置800万像素.机型代码 &#xff1a;nitrogen.官方最终版为稳定版12.5…

Vue3学习-day3

computed计算属性函数 计算属性小案例 App.vue <script setup> import { ref, computed } from vue// 声明数据 const list ref([1,2,3,4,5,6,7,8])// 基于list派生一个计算属性&#xff0c;从list中过滤出 > 2 const setList computed(() > {return list.val…

【博主推荐】VUE常见问题及解决方案

文章目录 1.找不到模块“../views/index.vue”或其相应的类型声明。ts(2307)2.当改变 Vue 实例中的数据时&#xff0c;视图没有相应地更新3.在某些复杂的异步操作或者多个数据交互场景下&#xff0c;数据绑定的更新在时间上出现延迟4.父组件无法将数据正确地传递给子组件&#…

完整化安装kubesphere,ks-jenkins的状态一直为init

错误描述&#xff1a; 打印日志&#xff1a; kubectl describe pod ks-jenkins-7fcff7857b-gh4g5 -n kubesphere-devops-system 日志描述如下&#xff1a; Events: Type Reason Age From Message ---- ------ ---- …

1.UGUI相关

1.这一种UIcanvas下的组件,会显示在3d物体之前 2.可以设置3d物体在UI界面之前。选中第二个模式。这时候会指定一个摄像机。一般情况下&#xff0c;不用主摄像机。需要新建一个专门给UI的摄像机。相当于设置距离摄像机的远近。两个layer 可以理解成 章节&#xff0c;关卡。相同…

OSPF - 2、3类LSA(Network-LSA、NetWork-Sunmmary-LSA)

前篇博客有对常用LSA的总结 2类LSA&#xff08;Network-LSA&#xff09; DR产生泛洪范围为本区域 作用:  描述MA网络拓扑信息和网络信息&#xff0c;拓扑信息主要描述当前MA网络中伪节点连接着哪几台路由。网络信息描述当前网络的 掩码和DR接口IP地址。 影响邻居建立中说到…

【数模学习笔记】插值算法和拟合算法

声明&#xff1a;以下笔记中的图片以及内容 均整理自“数学建模学习交流”清风老师的课程资料&#xff0c;仅用作学习交流使用 文章目录 插值算法定义三个类型插值举例插值多项式分段插值三角插值 一般插值多项式原理拉格朗日插值法龙格现象分段线性插值 牛顿插值法 Hermite埃尔…

1.2 WSL中安装Centos7

官网链接使用 WSL 访问网络应用程序 | Microsoft Learn 一、Win安装WSL配置 WSL官网链接使用 WSL 访问网络应用程序 | Microsoft Learn 1.1 命令模式开启虚拟化设置步骤 # 启用适用于 Linux 的 Windows 子系统&#xff1a;打开powershell并输入&#xff1a; dism.exe /onli…

有收到腾讯委托律师事务所向AppStore投诉带有【水印相机】主标题名称App的开发者吗

近期&#xff0c;有多名开发者反馈&#xff0c;收到来自腾讯科技 (深圳) 有限公司委托北京的一家**诚律师事务所卞&#xff0c;写给AppStore的投诉邮件。 邮件内容主要说的是&#xff0c;腾讯注册了【水印相机】这四个字的商标&#xff0c;所以你们这些在AppStore上的app&…

linux网络 | https前置知识 | 数据加密与解密、数据摘要

前言:本节内容讲述https的相关内容。 https博主会着重讲解https如何让一个请求和一个响应能够安全的进行交互。 https博主将用两篇文章进行讲解。本篇是两篇中第一篇。会把http的安全问题引出来&#xff0c; 然后说一下https的基本解决方法。 下面废话不多说&#xff0c; 开始我…