模拟实现定时器
关于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…...
shell脚本--常见案例
1、自动备份文件或目录 2、批量重命名文件 3、查找并删除指定名称的文件: 4、批量删除文件 5、查找并替换文件内容 6、批量创建文件 7、创建文件夹并移动文件 8、在文件夹中查找文件...
【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...
linux 错误码总结
1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...
【python异步多线程】异步多线程爬虫代码示例
claude生成的python多线程、异步代码示例,模拟20个网页的爬取,每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程:允许程序同时执行多个任务,提高IO密集型任务(如网络请求)的效率…...
Map相关知识
数据结构 二叉树 二叉树,顾名思义,每个节点最多有两个“叉”,也就是两个子节点,分别是左子 节点和右子节点。不过,二叉树并不要求每个节点都有两个子节点,有的节点只 有左子节点,有的节点只有…...
docker 部署发现spring.profiles.active 问题
报错: org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property spring.profiles.active imported from location class path resource [application-test.yml] is invalid in a profile specific resource [origin: class path re…...
关键领域软件测试的突围之路:如何破解安全与效率的平衡难题
在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件,这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下,实现高效测试与快速迭代?这一命题正考验着…...
腾讯云V3签名
想要接入腾讯云的Api,必然先按其文档计算出所要求的签名。 之前也调用过腾讯云的接口,但总是卡在签名这一步,最后放弃选择SDK,这次终于自己代码实现。 可能腾讯云翻新了接口文档,现在阅读起来,清晰了很多&…...
(一)单例模式
一、前言 单例模式属于六大创建型模式,即在软件设计过程中,主要关注创建对象的结果,并不关心创建对象的过程及细节。创建型设计模式将类对象的实例化过程进行抽象化接口设计,从而隐藏了类对象的实例是如何被创建的,封装了软件系统使用的具体对象类型。 六大创建型模式包括…...
Qt的学习(一)
1.什么是Qt Qt特指用来进行桌面应用开发(电脑上写的程序)涉及到的一套技术Qt无法开发网页前端,也不能开发移动应用。 客户端开发的重要任务:编写和用户交互的界面。一般来说和用户交互的界面,有两种典型风格&…...
