定时器的使用和实现

目录

一.定时器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,一经查实,立即删除!

相关文章

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

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

8. 正则表达式

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

高质量 HarmonyOS 权限管控流程

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

19、Go Gin框架集成Swagger

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

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

什么是数据仓库 &#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; 写一…

【背包-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;计算结构体中某变量相对于首地址的偏…

UE4获取动画序列资产的动画时长

谢谢”朝闻道“大佬的指点~

(UE4.26)UE4的FArchive序列化入门

前言 序列化(Serialize)和反序列化(UnSerialize)是程序领域常见的概念。对于这两个词汇我理解的是 序列化(Serialize): 变量值(int, float, string等基本类型, 或者Array&#xff0c;Map&#xff0c;或者更复杂的复合体)存储为一个文件(二进制流, 二进制文件, json, xml等格式…

C++并发之互斥(std::mutex)

目录 1 概述2 使用实例3 接口使用3.1 mutex3.2 lock3.3 try_lock3.4 unlock1 概述 互斥锁是一个可锁定的对象,用于在代码的关键部分需要独占访问时发出信号,防止具有相同保护的其他线程同时执行并访问相同的内存位置。   互斥对象提供独占所有权,不支持递归性(即,线程不…

Windows 找不到文件‘shell:sendto‘。请确定文件名是否正确后,再试一次

执行“shell:sendto”命令的时候&#xff0c;报错&#xff1a;Windows 找不到文件’shell:sendto’。请确定文件名是否正确后&#xff0c;再试一次 解决办法&#xff1a; 在桌面新建一个记事本文件命名为fix.reg&#xff0c;注意后缀是reg&#xff0c;文件中填写以下内容&…

应对转租、混租、群租,天诚人脸物联网智能门锁与公租房管理系统有一套!

“住房”关系着老百姓的切身利益和幸福指数。近年来&#xff0c;全国各地掀起了保障性住房建设热潮&#xff0c;积极有序推进智慧公租房小区打造&#xff0c;助力共同富裕。 一、公租房入住对象界定 公租房的受众群体各地标准不一&#xff0c;却也大同小异。重庆市公共租赁房…

【安装笔记-20240607-Linux-在 OpenWrt-23.05 上安装配置域名服务器】

安装笔记-系列文章目录 安装笔记-20240607-Linux-在 OpenWrt-23.05 上安装配置域名服务器 文章目录 安装笔记-系列文章目录安装笔记-20240607-Linux-在 OpenWrt-23.05 上安装配置域名服务器 前言一、软件介绍名称&#xff1a;Bind9主页官方介绍 二、安装步骤测试版本&#xff…

Liunx环境下redis主从集群搭建(保姆级教学)01

Linux 环境安装redis 准备一台linux虚拟机 我使用基于Linux的开源类服务器操作系统CentOS7。 打开虚拟机&#xff0c;输入密码登录 下载linux版本的redis安装包 已经下载redis-5.0.10.tar.gz 创建一个文件夹用来安装redis,我在/opt目录下创建redis文件夹 将下载好的redis…

Windows 更新根文件夹的修改时间

简介&#xff1a; Win10 系统不会根据深层目录文件更新主目录的修改时间. 一般解决办法是关闭 Winodws 搜索引擎。 win10文件夹不能自动更新了怎么办&#xff1f;_百度知道 本脚本通过递归遍历子目录和子文件&#xff0c;来更新根目录的时间。 使用内层目录和当前目录下的最新…

接口幂等性设计(5 大方案罗列)

结合案例、列举场景的接口幂等性设计方案。 方案 1. 状态机 业务场景&#xff0c;数据审核成功后进行短信通知&#xff0c;或者是订单状态变成已支付后&#xff0c;短信通知用户订单生成的详细信息&#xff0c;等等和状态有关的操作。 假设 status&#xff1a;0&#xff08;待…

查看服务器的硬件信息、操作系统等常用命令

在Linux下查看服务器是什么类型的服务器&#xff0c;通常可以通过查看服务器的硬件信息、操作系统和已安装的服务来判断。以下是一些常用的命令&#xff1a; 查看操作系统信息&#xff1a; cat /etc/*release* 查看CPU信息&#xff1a; lscpu 查看内存信息&#xff1a; free…