模拟实现定时器
关于java标准库中的定时器的使用可以看定时器Timer的使用
大致思路
定义一个MyTimeTask类,该类用于组织要执行任务的内容以及执行任务的时间戳,后面要根据当前系统时间以及执行任务的时间戳进行比较,来判断是否要执行任务或是要等待任务
用一个MyTimer类作为定时器的主体,在MyTimer类中用小根堆这个数据结构来存储要执行的任务
在MyTimer类中定义一个schedule方法,通过传入的参数来实例化一个任务,再将这个任务插入到小根堆中
在MyTimer类中创建一个扫描线程,一方面监控堆首元素是否到点了,一方面在到点后,要调用Runable中的run方法来执行任务,因为在创建MyTimer对象时就应该开始进行对任务的扫描,所以,扫描线程应该定义在MyTimer类的构造方法中
为什么要用小根堆来存储待执行的任务呢?
因为小根堆的堆顶是等待时间最短的任务,在众多任务中,我们只需要关注最早要进行的那个任务就可以了,要是最早要进行的任务都还不能执行,其他任务肯定都不能执行,如果我们不用小根堆的话,用链表的话,我们想要知道接下来要执行的任务就需要一直不停的遍历链表中的任务,将执行任务的时间戳与当前时间进行比较,一直不停的遍历代表一直占用cpu资源,这是不合理的。所以用小根堆来存储待执行的任务呢是比较合理的
MyTimeTask类的创建
代码展示
//描述要执行任务的内容,以及执行任务的时间戳
class MyTimeTask implements Comparable<MyTimeTask>{//由于该类的对象要存放到小根堆中,所以要实现Comparable接口,重写compareTo方法// 才可以给出合适的比较规则,才可以放入堆中@Overridepublic int compareTo(MyTimeTask o) {return (int)(this.time-o.time);}//要执行任务的内容private Runnable runnable;//执行任务的时间戳private long time;//构造函数传入要执行的任务以及等待的时间//delay是一个时间差,类似于3000这样的数值public MyTimeTask(Runnable runnable,long delay){this.runnable=runnable;time=System.currentTimeMillis()+delay; //当前系统时间加上延迟的时间就是任务要执行的时间戳}public Runnable getRunnable(){return runnable;}public long getTime(){return time;}
}
注意事项
1.由于MyTimeTask类中的time成员属性代表的是任务执行的时间戳,但是用户输入的参数delay是任务执行的延迟时间,所以任务执行的时间戳是当前系统时间加上延迟的时间
2.由于MyTimeTask类要添加到小根堆中,所以要先在MyTimeTask类中定义好放到堆中的优先级关系,这里采用MyTimeTask类实现Comparable接口,重写compareTo方法这个方法来规定MyTimeTask类的优先级关系
MyTimer类的创建
代码展示
/定时器类的主体
class MyTimer{//用小根堆这个数据结构来存储要执行的任务//因为小根堆堆顶的数是延迟时间最短,最快要执行的任务PriorityQueue<MyTimeTask> queue=new PriorityQueue<>();Object locker=new Object(); //用来加锁的锁对象//定时器的核心方法,将任务添加到堆中//涉及到多线程,可能有多个线程调用schedule添加任务到堆中,而且MyTimer内部的线程也要对堆进行修改//所以存在线程安全问题,并且schedule和扫描线程应该对同一个对象进行加锁public void schedule(Runnable runnable,long delay){synchronized(locker){MyTimeTask task=new MyTimeTask(runnable,delay);queue.offer(task);locker.notify();}}//定义一个扫描线程,一方面监控堆首元素是否到点了,一方面在到点后,要调用Runable中的run方法来执行任务//扫描线程不应该是用户自己调用的,而是一创建MyTimer对象就要有扫描线程来判断是否有需要执行的任务,所以应该写在构造方法中public MyTimer(){Thread t=new Thread(()->{synchronized(locker){ //因为接下来的代码中都涉及到对堆的操作,所以直接整体加上锁while (true){//wait推荐和while搭配使用,因为wait可能是不是被notify正常唤醒的,所以被唤醒了以后条件可能依然不满足// 所以要用while循环对条件是否满足进行多次判断while (queue.isEmpty()){//continue;try {locker.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}//取出任务判断是否需要执行,不删除MyTimeTask task=queue.peek();if(task.getTime()<=System.currentTimeMillis()){task.getRunnable().run(); //时间到了要执行任务queue.poll(); //执行任务后将任务从堆中弹出}else {//任务时间还没有到不需要执行,进行睡眠,等时间到了再执行try {//sleep不适合在这里用于进行休眠//1.sleep休眠不会释放锁,就代表在进行休眠的时候虽然线程没有对堆进行操作// 但locker对象还是被锁着的,此时要往队列中添加数据也要进行等待,这是不合理的//2.如果我们新增了一个任务,可能这条任务的执行时间比当前堆顶最近的执行时间还要早进行//此时我们就需要从睡眠状态脱离,去检查是否要更改随眠时间,但sleep睡眠的过程中不方便提前中断//虽然可以用interrupt提前中断,但是使用interrupt意味着程序已经要结束了//所以使用wait来进行休眠是比sleep更加合理的//Thread.sleep(task.getTime()-System.currentTimeMillis());locker.wait(task.getTime()-System.currentTimeMillis());} catch (InterruptedException e) {throw new RuntimeException(e);}}}}});//线程定义完毕,启动线程t.start();}}
注意事项
1.定义一个schedule方法,用户通过传入任务执行内容以及延迟时间来使用该方法,在schedule中,通过用户传入的参数来实例化一个MyTimeTask的任务,将该任务添加到小根堆中。
2.要进行执行任务的话要在MyTimer内部的扫描线程中执行,由于MyTimer类实例化以后就需要判断是否有新的任务需要处理,所以扫描线程应该创建在MyTimer类的构造方法中,通过循环扫描堆顶的的任务,当堆为空的时候就进入wait睡眠直到用户调用schedule方法向堆中传入任务的时候才调用notify解除睡眠,关于wait和notify可以看通过wait和notify来协调线程执行顺序
当堆不为空的时候,就要取出堆顶的任务根据执行任务的时间戳以及当期系统时间来判断是否需要执行,不删除,要是当前系统时间以及>=任务时间的话,就需要调用task.getRunnable().run()来执行任务,要是当前系统时间以及<任务时间就需要进行wait睡眠,睡眠的时间便是任务执行的时间戳-当前系统时间,因为当前所扫描的这个任务便是最早执行的了,所以其他任务没有扫描的必要,它们更完执行
3.为什么要schedule方法和扫描线程加上synchronized锁呢(关于synchronized锁可以看线程安全问题),因为schedule方法涉及到向堆中添加数据,要是多个线程调用schedule方法就会出现线程安全问题,而扫描线程中也有多次涉及到堆的使用,所以也同样具有线程安全问题,所以要加上synchronized锁来保证线程安全,并且因为schedule方法和扫描线程都是对堆进行修改,所以它们要对同一个对象加锁。
4.sleep同样也可以1进行睡眠,为什么我们要用wait,notify呢
(1).sleep休眠不会释放锁,就代表在进行休眠的时候虽然线程没有对堆进行操作, 但locker对象还是被锁着的,此时要往队列中添加数据也要进行等待,这是不合理的
(2).如果我们新增了一个任务,可能这条任务的执行时间比当前堆顶最近的执行时间还要早进行,此时我们就需要从睡眠状态脱离,去检查是否要更改随眠时间,但sleep睡眠的过程中不方便提前中断,虽然可以用interrupt提前中断,但是使用interrupt意味着程序已经要结束了
所以使用wait来进行休眠是比sleep更加合理的
相关文章:
模拟实现定时器
关于java标准库中的定时器的使用可以看定时器Timer的使用 大致思路 定义一个MyTimeTask类,该类用于组织要执行任务的内容以及执行任务的时间戳,后面要根据当前系统时间以及执行任务的时间戳进行比较,来判断是否要执行任务或是要等待任务 用一…...
TCP/IP的分包粘包
TCP/IP的分包粘包 分包粘包介绍导致分包粘包的原因导致TCP粘包的原因:导致TCP分包的原因:避免分包粘包的措施 分包粘包介绍 因为TCP为了减少额外开销,采取的是流式传输,所以接收端在一次接收的时候有可能一次接收多个包。而TCP粘…...
盘点:查快递教程
在“寄快递”成为常态的当下,如何快速进行物流信息查询,是收寄人所关心的问题。在回答这个问题之前,首先我们要知道,物流信息查询,有哪些方法? 1、官网单号查询 知道物流公司和单号的情况下,直…...
TransGPT 开源交通大模型开源
TransGPT 是开源交通大模型,主要致力于在真实交通行业中发挥实际价值。 它能够实现交通情况预测、智能咨询助手、公共交通服务、交通规划设计、交通安全教育、协助管理、交通事故报告和分析、自动驾驶辅助系统等功能。 TransGPT 作为一个通用常识交通大模型&#…...
gitignore文件使用方法(gitignore教程)(git status --ignored)(git check-ignore -v <file>)
文章目录 Gitignore文件使用描述Gitignore基本语法1. 基本语法★★★★★2. 配置方法 匹配示例示例1示例2示例3 其他命令git status --ignored(用于显示被Git忽略的文件和文件夹的状态)git check-ignore -v <file>(用于检查指定文件是否…...
mybatis拼接sql导致的oom报错 GC报错
报错1:mybatis拼接过多 java.lang.OutOfMemoryError: GC overhead limit exceeded 具体报错: nested exception is org.apache.ibatis.builder.BuilderException: Error evaluating expression ew.sqlSegment ! null and ew.sqlSegment ! and ew.non…...
如何通俗理解扩散模型?
扩散模型(Diffusion Model)是一类十分先进的基于扩散思想的深度学习生 成模型。生成模型除了扩散模型之外,还有出现较早的 VAE ( Variational Auto- Encoder,变分自编码器) 和 GAN ( Generative Adversarial Net ,生成对抗网络) 等。 虽然它们…...
【C#】并行编程实战:并行编程中的模式
本章将介绍并行编程模式,重点是理解并行代码问题场景并使用并行编程/异步技术解决他们。本章会介绍几种最重要的编程模式。 本教程学习工程:魔术师Dix / HandsOnParallelProgramming GitCode 1、MapReduce 模式 引入 MapReduce 是为了解决处理大数据的问…...
Apache Kafka 入门教程
Apache Kafka 入门教程 一、简介简介架构 二、Kafka 安装和配置JDK安装 Kafka配置文件详解 三、Kafka 的基本操作启动和关闭Topic 创建和删除Partitions 和 Replication 配置Producer 和 Consumer 使用方法ProducerConsumer 四、Kafka 高级应用消息的可靠性保证Kafka StreamKaf…...
python皮卡丘编程代码教程,用python打印皮卡丘
大家好,小编来为大家解答以下问题,如何用print函数打印一只皮卡丘,用python如何打印丘比特之心,现在让我们一起来看看吧!...
shell脚本:数据库的分库分表
#!/bin/bash ######################### #File name:db_fen.sh #Version:v1.0 #Email:admintest.com #Created time:2023-07-29 09:18:52 #Description: ########################## MySQL连接信息 db_user"root" db_password"RedHat123" db_cmd"-u${…...
AtCoder Beginner Contest 312(A~D)
A //语法题也要更仔细嘞,要不然也会wa #include <bits/stdc.h> // #pragma GCC optimize(3,"Ofast","inline") // #pragma GCC optimize(2) using namespace std; typedef long long LL; #define int LL typedef pair<int, int> …...
SQL中Partition的相关用法
使用Partition可以根据指定的列或表达式将数据分成多个分区。每个分区都是逻辑上独立的,可以单独进行查询、插入、更新和删除操作。Partition可以提高查询性能,因为它可以限制在特定分区上执行查询,而不是在整个表上执行。 在SQL中ÿ…...
微服务——Docker
docker与虚拟机的区别 首先要知道三个层次 硬件层:计算机硬件 内核层:与硬件交互,提供操作硬件的指令 应用层: 系统应用封装内核指令为函数,便于程序员调用。用户程序基于系统函数库实现功能。 docker在打包的时候直接把应用层的函数库也进行打包&a…...
测试|测试用例方法篇
测试|测试用例方法篇 文章目录 测试|测试用例方法篇1.测试用例的基本要素:测试环境,操作步骤,测试数据,预期结果…2.测试用例带来的好处3.测试用例的设计思路,设计方法,具体设计方法之间的关系**设计测试用…...
负载均衡的策略有哪些? 负载均衡的三种方式?
负载均衡的策略有哪些? 负载均衡的策略有如下: 1. 轮询(Round Robin):按照请求的顺序轮流分配到不同的服务器。 2. 权重(Weighted):给不同的服务器分配不同的权重,根据权重比例来…...
二十三章:抗对抗性操纵的弱监督和半监督语义分割的属性解释
0.摘要 弱监督语义分割从分类器中生成像素级定位,但往往会限制其关注目标对象的一个小的区域。AdvCAM是一种图像的属性图,通过增加分类分数来进行操作。这种操作以反对抗的方式实现,沿着像素梯度的相反方向扰动图像。它迫使最初被认为不具有区…...
curator实现的zookeeper可重入锁
Curator是一个Apache开源的ZooKeeper客户端库,它提供了许多高级特性和工具类,用于简化在分布式环境中使用ZooKeeper的开发。其中之一就是可重入锁。 Curator提供了InterProcessMutex类来实现可重入锁。以下是使用Curator实现ZooKeeper可重入锁的示例&am…...
抽象工厂模式——产品族的创建
1、简介 1.1、简介 抽象工厂模式为创建一组对象提供了一种解决方案。与工厂方法模式相比,抽象工厂模式中的具体工厂不只是创建一种产品,它负责创建一族产品 1.2、定义 抽象工厂模式(Abstract Factory Pattern):提供…...
【C语言初阶篇】自定义类型结构体我不允许还有人不会!
🎬 鸽芷咕:个人主页 🔥 个人专栏:《C语言初阶篇》 《C语言进阶篇》 ⛺️生活的理想,就是为了理想的生活! 文章目录 📋 前言1 . 什么是结构体1.1 结构的定义1.2 结构的声明 2.结构体初始化2.1 用标签名定义和初始化2.2…...
接口测试中缓存处理策略
在接口测试中,缓存处理策略是一个关键环节,直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性,避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明: 一、缓存处理的核…...
Docker 离线安装指南
参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性,不同版本的Docker对内核版本有不同要求。例如,Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本,Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...
React hook之useRef
React useRef 详解 useRef 是 React 提供的一个 Hook,用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途,下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...
三维GIS开发cesium智慧地铁教程(5)Cesium相机控制
一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点: 路径验证:确保相对路径.…...
基础测试工具使用经验
背景 vtune,perf, nsight system等基础测试工具,都是用过的,但是没有记录,都逐渐忘了。所以写这篇博客总结记录一下,只要以后发现新的用法,就记得来编辑补充一下 perf 比较基础的用法: 先改这…...
苍穹外卖--缓存菜品
1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据,减少数据库查询操作。 缓存逻辑分析: ①每个分类下的菜品保持一份缓存数据…...
BCS 2025|百度副总裁陈洋:智能体在安全领域的应用实践
6月5日,2025全球数字经济大会数字安全主论坛暨北京网络安全大会在国家会议中心隆重开幕。百度副总裁陈洋受邀出席,并作《智能体在安全领域的应用实践》主题演讲,分享了在智能体在安全领域的突破性实践。他指出,百度通过将安全能力…...
leetcodeSQL解题:3564. 季节性销售分析
leetcodeSQL解题:3564. 季节性销售分析 题目: 表:sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...
【C语言练习】080. 使用C语言实现简单的数据库操作
080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...
html-<abbr> 缩写或首字母缩略词
定义与作用 <abbr> 标签用于表示缩写或首字母缩略词,它可以帮助用户更好地理解缩写的含义,尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时,会显示一个提示框。 示例&#x…...
