定时器的使用和实现

目录

一.定时器Timer类的主要方法

二.定时器Timer类的使用 

三.定时器的模拟实现


一.定时器Timer类的主要方法

定时器Timer类在java.util包中。

使用前先进行实例化,然后使用实例的schedule(TimerTask task, long delay)方法,设定指定的任务task在指定的延迟delay (ms)后运行。要安排的任务就是一个Runnable,定时器任务类TimerTask是抽象类,继承TimerTask并重写其run()方法,可实现要完成的任务。
schedule(TimerTask task, Date time)将指定的任务安排在指定的时间执行,如果时间是过去的时间,任务将被调度为立即执行
cancel()方法结束这个定时器。
要实现一个定时任务,运用java中的Timer类和TimerTask类能够现实时调用处理函数。

二.定时器Timer类的使用 

 一个定时器,可以同时往里面安排多个任务

import java.util.Timer;
import java.util.TimerTask;public class Demo3 {public static void main(String[] args) {Timer timer = new Timer();//先实例化一个对象timer.schedule(new TimerTask() {@Overridepublic void run() {//重写run方法System.out.println("2.时间到,该写作业了");}},4000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("3.时间到,该吃饭了");}},5000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("1.时间到,该学习了");}},3000);System.out.println("开始计时");}
}

运行结果:

运行的顺序时按照延迟的时间来运行的

在第二个任务的run方法中加入cancel就会在当前正在执行的任务结束后,结束进程,不会再继续执行后面剩余的任务

    timer.schedule(new TimerTask() {@Overridepublic void run() {//重写run方法System.out.println("2.时间到,该写作业了");timer.cancel();}},4000);

 运行结果:

三.定时器的模拟实现

 schedule的第一个参数是一个要执行的任务

需要描述这个任务就包含两个方面的信息,一个是要执行啥工作,另一个是啥时候执行

class MyTask {private Runnable runnable;//执行的任务private long time;//什么时候执行,是一个时间戳public MyTask(Runnable runnable,long delay){this.runnable = runnable;this.time = System.currentTimeMillis()+delay;}
}

Timer是能够安排多个任务的,而不同的任务执行的时间又不相同,就需要有一个存储空间来存储

如果使用ArrayList的话,里面的元素是无序的,每次安排进去一个新的任务就得进行一次查找当前最先执行的任务,需要耗费O(n)的时间,所以需要一个每次一拿就能拿到最先执行的任务,就需要使用优先级队列,但是优先级队列PriorityQueue是线程不安全的,而我们的schedule是可能在多线程当中进行调用,就可能会出现问题,而阻塞队列的线程是安全的,使用优先级阻塞队列能够完成这项工作

BlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();

 此时安排任务就是有序又线程安全的了

有了这个队列后就需要创建一个单独的线程不断地来进行扫描队首元素,查看时间是否到了,到了就执行任务,而阻塞队列无法阻塞的取队首元素,所以时间没到就得把任务给放回去

MyTask2里面还需要有两个get方法来让外面的程序拿到任务和时间

public Runnable getRunnable() {return runnable;
}public long getTime() {return time;
}

还需要一个schedule方法来安排任务,将任务放进去队列当中去

class MyTimer2{private BlockingQueue<MyTask2> queue = new PriorityBlockingQueue<MyTask2>();public MyTimer2(){Thread t = new Thread(() -> {while(true) {try {MyTask2 task2 = queue.take();//获取首任务if (System.currentTimeMillis() >= task2.getTime()) {//当前运行时间和任务开始执行的时间进行对比task2.getRunnable().run();//时间到了,开始执行任务} else {//时间未到,放回队列queue.put(task2);}} catch (InterruptedException e) {throw new RuntimeException(e);}}});t.start();//启动线程}public void schedule(Runnable runnable,long after) throws InterruptedException {MyTask2 myTask2 = new MyTask2(runnable,after);//创建一个新的任务queue.put(myTask2);//将任务放进队列}
}
public class Demo5 {public static void main(String[] args) throws InterruptedException {MyTimer2 timer = new MyTimer2();timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("时间到!");}},5000);timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("剩余1秒!");}},4000);timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("剩余2秒!");}},3000);timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("剩余3秒!");}},2000);System.out.println("开始计时!");}
}

此时运行程序的时候就会发现一个问题

 发生了类型转换异常,所以在MyTask2里面还需要一个比较器

    public int compareTo(MyTask2 o) {return (int) (this.time - o.time);//此处得判断是谁减谁,可以让程序运行一下就可以判断,还需要进行类型转换}

 运行结果:

 

 但此时程序严重的问题

即程序一直处于循环的等待,等待过程中光看时间,完全做不了别的事情,而看时间对整个任务的进程没啥影响,此时CPU没有空闲出来,这个循环就是在忙等,那这里的等待就没有意义

例如:

如果使用sleep方法的话并不太行,因为如果当前的时间是0:00而第一个任务的执行时间是2:00,此时需要sleep2个小时,然后在这个过程中加入了一个新的任务在1:00执行,但此时程序sleep了,该任务就会错过执行的时间,导致工作线程无法在1:00执行此任务

使用wait和notify就可以解决这个问题

class MyTimer2{private BlockingQueue<MyTask2> queue = new PriorityBlockingQueue<MyTask2>();public Object locker = new Object();public MyTimer2(){Thread t = new Thread(() -> {while(true) {try {MyTask2 task2 = queue.take();long curTime = System.currentTimeMillis();if (curTime >= task2.getTime()) {//当前运行时间和任务开始执行的时间进行对比task2.getRunnable().run();//时间到了,开始执行任务} else {//时间未到,放回队列queue.put(task2);synchronized (locker){locker.wait(task2.getTime() - curTime);}}} catch (InterruptedException e) {throw new RuntimeException(e);}}});t.start();//启动线程}public void schedule(Runnable runnable,long after) throws InterruptedException {MyTask2 myTask2 = new MyTask2(runnable,after);//创建一个新的任务queue.put(myTask2);//将任务放进队列synchronized (locker){locker.notify();//当加入一个新的任务后,notify能够唤醒上方的wait,扫描线程将会重新获取首任务,再次判断}}
}

但该程序仍存在一个问题

 因为当前的环境是多线程的环境,所以如果扫描线程拿到首任务task2后,然后再wait等待之前,线程切换到了schedule线程,schedule线程新增一个新的任务,而当前新的任务执行时间是在上面所取到的task2之前,然后线程切换回MyTimer2,从刚刚的地方继续往下执行,然后就会进行wait等待task2开始执行的时间,一样会造成新任务没办法按时执行,该问题产生的原因就是因为操作不是原子的

 所以需要将上方第一把锁的范围扩大,读比等操作都在同一把锁里面

public MyTimer2(){Thread t = new Thread(() -> {while(true) {try {synchronized (locker){//将🔒的范围扩大MyTask2 task2 = queue.take();long curTime = System.currentTimeMillis();if (curTime >= task2.getTime()) {//当前运行时间和任务开始执行的时间进行对比task2.getRunnable().run();//时间到了,开始执行任务} else {//时间未到,放回队列queue.put(task2);locker.wait(task2.getTime() - curTime);}}} catch (InterruptedException e) {throw new RuntimeException(e);}}});t.start();
}

此时将锁的范围扩大后就可以避免了notify在take和wait之间执行,扫描线程会先拿到锁,然后take,然后中间逻辑,一直到wait

在这个过程中,schedule线程会阻塞等待锁,直到扫描线程执行到了wait之后,扫描线程释放了锁schedule线程就拿到了锁,新增任务后,notify通知,wait就被立刻唤醒了,接下来重新获取队首元素,就把新增的任务取出来了

完整代码

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.PriorityBlockingQueue;class MyTask2 implements Comparable<MyTask2>{private Runnable runnable;private long time;public MyTask2(Runnable runnable, long delay){this.runnable = runnable;this.time = System.currentTimeMillis() + delay;}public Runnable getRunnable() {return runnable;}public long getTime() {return time;}@Overridepublic int compareTo(MyTask2 o) {return (int) (this.time - o.time);}
}class MyTimer2{private BlockingQueue<MyTask2> queue = new PriorityBlockingQueue<MyTask2>();public Object locker = new Object();public MyTimer2(){Thread t = new Thread(() -> {while(true) {try {synchronized (locker){MyTask2 task2 = queue.take();long curTime = System.currentTimeMillis();if (curTime >= task2.getTime()) {//当前运行时间和任务开始执行的时间进行对比task2.getRunnable().run();//时间到了,开始执行任务} else {//时间未到,放回队列queue.put(task2);locker.wait(task2.getTime() - curTime);}}} catch (InterruptedException e) {throw new RuntimeException(e);}}});t.start();}public void schedule(Runnable runnable,long after) throws InterruptedException {MyTask2 myTask2 = new MyTask2(runnable,after);//创建一个新的任务queue.put(myTask2);//将任务放进队列synchronized (locker){locker.notify();}}
}public class Demo5 {public static void main(String[] args) throws InterruptedException {MyTimer2 timer = new MyTimer2();timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("时间到!");}},5000);timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("剩余1秒!");}},4000);timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("剩余2秒!");}},3000);timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("剩余3秒!");}},2000);System.out.println("开始计时!");}
}

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

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

相关文章

微信小程序按钮设计与交互:打造极致用户体验

微信小程序作为一种流行的应用形式&#xff0c;其界面设计和交互体验对于用户吸引力和留存率至关重要。其中&#xff0c;按钮作为用户与小程序进行交互的主要方式之一&#xff0c;其设计和实现直接影响到用户体验的质量。在本文中&#xff0c;我们将探讨微信小程序按钮的设计与…

因为附件服务重启的问题

生产的服务器出现以下现象&#xff1a; 1.API报503&#xff0c; 2.不停的重启 发现是OOM的错误。 作为经验法则&#xff0c;有几种常见的OOM情况。 堆内存溢出。如内存泄漏。 虚拟机栈和本地方法栈溢出。如有无限的递归方法。 JVM内存消耗超过容器限制。 从上面的截图中&…

python数据分析-心脏瓣膜手术风险分析与预测

研究背景 人的心脏有四个瓣膜&#xff0c;主动脉银、二尖、肺动脉和三尖源 不管是那一个膜发生了病变&#xff0c;都会导致心脏内的血流受到影响&#xff0c;这就是通常所说的心脏期膜病&#xff0c;很多是需要通过手术的方式进行改善的。随着人口老龄化的加剧,&#xff0c;心…

8. 正则表达式

正则表达式 在处理字符串时&#xff0c;需要查找符合某些复杂规则的字符串&#xff0c;正则表达式就是用于描述这些规则的工具 一、正则表达式语法 行定位符&#xff1a;用来描述字符串的边界 -->用来匹配一整行 符号匹配位置^行的开始$行的结尾 ^tm : 可以匹配行 tm equa…

DevOps 安全集成:从开发到部署,全生命周期安全守护

目录 一、DevOps 安全集成&#xff1a;为什么要做&#xff1f; 二、DevOps 安全集成&#xff1a;如何做&#xff1f; 三、DevOps 安全集成的优势 四、DevOps 安全集成&#xff1a;一些最佳实践 五、DevOps 安全集成&#xff1a;未来展望 六、思考与建议 七、总结 DevOps…

Perl 运算符

Perl 运算符 Perl 是一种功能强大的编程语言,广泛应用于系统管理、网络编程、GUI 创建、数据库访问等众多领域。Perl 的语法灵活,支持多种编程范式,包括过程式、面向对象和函数式编程。在 Perl 中,运算符扮演着重要的角色,它们用于执行各种操作,如算术运算、比较、赋值等…

高质量 HarmonyOS 权限管控流程

高质量 HarmonyOS 权限管控流程 在 HarmonyOS 应用开发过程中&#xff0c;往往会涉及到敏感数据和硬件资源的调动和访问&#xff0c;而这部分的调用就会涉及到管控这部分的知识和内容了。我们需要对它有所了解&#xff0c;才可以在应用开发中提高效率和避免踩坑。 权限管控了…

19、Go Gin框架集成Swagger

介绍&#xff1a; Swagger 支持在 Gin 路由中使用一系列注释来描述 API 的各个方面。以下是一些常用的 Swagger 注释属性&#xff0c;这些属性可以在 Gin 路由的注释中使用&#xff1a; Summary: 路由的简短摘要。Description: 路由的详细描述。Tags: 用于对路由进行分类的标…

跟我学C++高级篇——回调函数及应用

一、回调函数 什么是回调函数&#xff1f;顾名思意&#xff0c;回调函数就是调用方被&#xff08;被调用方&#xff09;调用&#xff0c;有点绕口啊。一般的函数调用&#xff0c;都是一方向另一方发起调用&#xff0c;然后得到调用的结果。一般情况下&#xff0c;回调函数通过…

Unity中的CanvasScaler组件讲解

Unity中的CanvasScaler组件是UGUI系统中的一个关键组件&#xff0c;主要用于控制画布的缩放和适配&#xff0c;以确保UI在不同屏幕分辨率下的显示效果一致。以下是关于CanvasScaler组件的详细讲解&#xff1a; 一、CanvasScaler组件的作用 调整UI画布的缩放和分辨率适配&…

数据挖掘--数据仓库与联机分析处理

什么是数据仓库 &#xff08;面集时非&#xff09; 面向主题的&#xff1a;围绕某一主题来构建集成的&#xff1a;图片文字杂糅在一起时变的&#xff1a;随时间变化的数据非易失的&#xff1a;硬盘存放&#xff0c;不易丢失 操作数据库系统&#xff08;OLTP)与数据仓库(OLAP…

MySQL将错乱的水果信息,截取展示为 品名 英文名 价格 三列展示

将错乱的水果信息&#xff0c;截取展示为 品名 英文名 价格 三列展示 idname1苹果Apple72Plum6李子3Pineapple8菠萝4Mango5芒果5龙吐珠5Buddha’sHand6Olive9橄榄7Raspberry4树莓8Apricot5杏子9Grapefruit9柚子10火龙果Dragonfruit911倒挂金钟Hanging6LobsterClaw12巨峰葡萄Co…

AI办公自动化:批量把docx文档转换为txt文本

任务&#xff1a;把docx文档批量转换成txt&#xff0c;首先让deepseek写了一段代码&#xff0c;但是转换失败。用的是最流行的python-docx库来读取docx文档&#xff0c;但是始终无法读取成功&#xff0c;换成pywin32库就解决问题了。 在deepseek中输入提示词&#xff1a; 写一…

「前端+鸿蒙」鸿蒙应用开发-真机运行

在鸿蒙应用开发中&#xff0c;真机运行是验证应用在实际硬件上表现的重要步骤。以下是如何在华为DevEco Studio中配置真机运行的详细步骤&#xff0c;以及相应的示例代码。 快速体验-真机运行 准备工作&#xff1a; 确保您的鸿蒙设备已开启开发者模式&#xff0c;并启用USB调试…

部件库(Widget Factory)

部件库(Widget Factory) 部件库,也被称为Widget Factory,是一个强大的工具,用于创建、存储和管理可重用的软件组件。在本文中,我们将深入探讨部件库的概念、重要性、以及如何在现代软件开发中使用它。 什么是部件库? 部件库是一个集合,其中包含了各种预先构建的软件…

c++ 简单的日志类 CCLog

此日志类&#xff0c;简单地实现了向标准输出控制台和文件输出日志信息的功能&#xff0c;并能在这两者之间进行切换输出&#xff0c;满足输出日志的不同需求。 代码如下&#xff1a; /** CCLog.h* c_common_codes** Created by xichen on 12-1-12.* Copyright 2012 cc_te…

40.任务调度线程池

Timer(废弃) 在任务调度线程池功能加入之前,可以使用java.util.Timer来实现定时功能,Timer优点在于简单易用,缺点是由于所有的任务都是由同一个线程来调度,因此所有的任务都是串行执行,同一时间只能有一个任务在执行,前一个任务的延迟和异常都将会影响之后的任务。 T…

【背包-BM70 兑换零钱(一)】

题目 BM70 兑换零钱(一) 描述 给定数组arr&#xff0c;arr中所有的值都为正整数且不重复。每个值代表一种面值的货币&#xff0c;每种面值的货币可以使用任意张&#xff0c;再给定一个aim&#xff0c;代表要找的钱数&#xff0c;求组成aim的最少货币数。 如果无解&#xff0c;…

docker 命令 ps,inspect,top,logs详解

docker常用命令教程-4 docker ps docker ps 命令用于列出当前正在运行的容器。默认情况下&#xff0c;它只显示正在运行的容器&#xff0c;但你可以使用 -a 或 --all 选项来显示所有容器&#xff08;包括已停止的容器&#xff09;。 常用的选项和示例&#xff1a; -a 或 --…

【C语言题解】1、写一个宏来计算结构体中某成员相对于首地址的偏移量;2、写一个宏来交换一个整数二进制的奇偶位

&#x1f970;欢迎关注 轻松拿捏C语言系列&#xff0c;来和 小哇 一起进步&#xff01;✊ &#x1f308;感谢大家的阅读、点赞、收藏和关注 &#x1f495;希望大家喜欢我本次的讲解&#x1f495; 目录&#x1f451; 1、写一个宏&#xff0c;计算结构体中某变量相对于首地址的偏…