【JavaEE初阶 — 多线程】定时器的应用及模拟实现

     c96f743646e841f8bb30b2d242197f2f.gif

ddb5ae16fc92401ea95b48766cb03d96.jpeg692a78aa0ec843629a817408c97a8b84.gif

目录

    1. 标准库中的定时器    

     1.1 Timer 的定义    

     1.2 Timer 的原理     

     1.3 Timer 的使用    

    1.4 Timer 的弊端    

     1.5 ScheduledExecutorService     

    2. 模拟实现定时器    

    2.1 实现定时器的步骤     

    2.1.1  定义类描述任务    

     定义类描述任务    

     第一种定义方法     

    第二种定义方法    

     定义 MyTimerTask 类的时间属性    

     完善描述任务类的比较规则    

     2.1.2 使用优先级队列实现 Timer    

     2.1.3  实现 schedule() 方法    

     对该方法的简单说明    

    把任务添加到队列中    

     2.1.4 扫描线程中负责执行队列中的任务    

     创建 worker    

     模拟队列为空时的阻塞效果    

     完善对任务的时间属性进行比较的操作     

     处理线程安全问题     

     安排任务    

     程序运行结果    

     2.2 忙等问题    

     2.3 模拟实现定时器完整代码    

     3.  拓展时间轮     

     4. 优先级队列 & 时间轮的优缺点    



    1. 标准库中的定时器    


     1.1 Timer 的定义    


  • Java 的Timer是一个用于调度任务的工具类,用于在未来某个时刻执行任务或周期性地执行任务。
  • Timer类一般与 TimerTask 搭配使用,其中 TimerTask 是一个需要执行的任务。
  • 适用于简单的定时任务,如定时更新、定期发送报告等。

     1.2 Timer 的原理     


Timer可以实现延时任务,也可以实现周期性任务,它的核心就是一个优先队列和封装的执行任务的线程。


    实现原理    


  • 维持一个小顶堆,即最快需要执行的任务排在优先队列的第一个,根据堆的特性我们知道插入和删除的时间复杂度都是O(log n)。
  • 然后有个TimerThread线程,不断地拿排着的 第一个任务 的执行时间,和当前时间做对比。
  • 如果时间到了先看看这个任务是不是周期性执行的任务:
  • 如果是周期性任务,则修改当前任务时间为下次执行的时间;
  • 如果不是周期性任务,则将任务从优先队列中移除,最后执行任务。
  • 如果时间还未到则调用wait() 等待。

可以看出Timer,实际就是根据任务的执行时间,维护了一个优先队列,并且起了一个线程,不断地拉取任务执行。


     1.3 Timer 的使用    


编译器一般都是Runnable 来描述任务,但是在定时器这里稍微特殊一点,把 Runnable 封装成了  TimerTask;TimeTask 本质上是一个抽象类:


    示例一     

    程序运行结果    

进程一直没有结束,是因为 Timer 和线程池一样 ,都包含了前台线程,阻止进程结束;


    1.4 Timer 的弊端    


  • 首先优先队列的插入和删除的时间复杂度是O(logn),当数据量大的时候,频繁的入堆出堆性能有待考虑。
  • 并且是单线程执行,那么如果一个任务执行的时间过久,则会影响下一个任务的执行时间(当然我们设置任务的 run(),要是异步执行也行)。
  • 并且从它对异常没有做什么处理,所以一个任务出错的时候会导致之后的任务都无法执行。

     1.5 ScheduledExecutorService     


  • ScheduledExecutorService 是 Java5 引入的替代方案,功能更强大。
  • 它支持多线程并行调度任务,能更好地处理任务调度的复杂场景
  • 因为使用线程池进行任务调度,所以不会因某个任务的异常终止,而导致其他任务停止。

  • 并且它提供了更灵活的API,可以更精细地控制任务的执行周期和策略。
  • 推荐使用ScheduledExecutorService 替代Timer。

    示例二    

     注意事项     


    程序运行结果    


    2. 模拟实现定时器    


    2.1 实现定时器的步骤     


    2.1.1  定义类描述任务    


     定义类描述任务    

通过 MyTimerTask类 来描述,在定时器定时的时间结束后,线程要执行的任务 :


     第一种定义方法     

基于抽象类的方式定义MyTimerTask,并实现 Runnable 接口,并且重写 run();

这样的定义虽然确实可以,写起来有点麻烦,还有另外的写法;


    第二种定义方法    

可以不把 MyTimerTask类 设置成抽象类,而是在MyTimerTask类的成员中,持有一个 Runnable:

后续通过构造方法参数,把定义的任务传进来:

上述两种写法都是可以的;  


     定义 MyTimerTask 类的时间属性    

MyTimerTask 不但要描述要执行的任务,还要记录什么时候任务被执行:

并提供相应的方法来获取任务执行时间,和任务执行方法:


     完善描述任务类的比较规则    

  • 因为要通过优先级队列比较任务的执行时间,对于要比较的元素是 int,String 这种本身就有明确比较规则的对象,可以不额外指定;
  • 但是我们自己定义的类 MyTimerTask 是没有明确比较规则的,所以我们需要给  MyTimerTask 实现比较规则,实现 Comparable 接口,重写 compareTo() 方法;

  • 这个优先级队列是小根堆,让时间少的任务先执行,如果分不清楚建立的是小根堆还是大根堆,就排列组合,直到找到合适的计算表达式;
  • time 之间的计算结果是 long 类型的,compareTo() (是优先级队列内部调用的),返回类型是 int,所以需要强转;

     2.1.2 使用优先级队列实现 Timer    



  • 定时器的构成是一个优先级队列(不要使用PriorityBlockingQueue,容易死锁);
  • 队列中的每个元素是一个 Task 对象,因此我们自己实现 Timer,可以把泛型参数设置成刚刚实现的,用来描述任务的类 MyTimerTask:


     2.1.3  实现 schedule() 方法    


     对该方法的简单说明    


    把任务添加到队列中    


     2.1.4 扫描线程中负责执行队列中的任务    


     创建 worker    

同时有一个worker线程一直扫描队首元素,看队首元素是否需要执行 ;所以通过 MyTimer 的构造方法,来创建 Thread 对象:

task 中带有一个时间属性,队首元素就是即将要执行的任务。


     模拟队列为空时的阻塞效果    

如果在往队列中取任务的时候,发现队列为空,则模拟出线程被队列阻塞的效果:

只有当任务执行完才可以将其移除出优先级队列,否则只能通过 peek() 取任务;


     完善对任务的时间属性进行比较的操作     

和线程池不同,线程池是只要队列不为空,就立即取任务并执行; 

但是 worker 需要关注队首元素的时间属性,系统时间到了任务执行时间,队首元素才会被取出并且执行,否则时间不到,任务不能执行:

在拿到任务后,我们需要比较当前系统时间是否已经到任务执行时间,没有到就继续通过 continue 的方式模拟阻塞 ,否则执行该任务,并且在执行完后出队列。


     处理线程安全问题     

当前调用 schedule 是一个线程,定时器内部又有一个线程,多个线程操作同一个队列,一定涉及到线程安全问题,所以我们要给 submit()  和 worker 中的操作加锁:

构造方法本身可以写synchronized,但是这个地方不能这么写,要保护的逻辑是 lambda表达式中的 run() ,run() 和构造方法是两个不同的方法;


     安排任务    


     程序运行结果    

  • worker 需要关注队首元素的时间属性,系统时间到了任务执行时间,队首元素才会被取出并且执行,否则时间不到,任务不能执行;
  • 和线程池不同,线程池是只要队列不为空,就立即取任务并执行; 
  • worker 在发现队列为空时,会陷入 continue 模拟出来的阻塞等待,进程继续保持运行状态;

     2.2 忙等问题    


    什么是忙等    


  • 定时器在执行上述任务,会出现忙等问题,忙等并没有实质性地做任何工作,只是在等待。
  • 但是忙等又和 sleep 这样的等待不同:
  • sleep 这样的等待是会释放 CPU 资源的等待,如果这个线程什么都不干,就不参与CPU资源的调度,把CPU资源让给其他线程;
  • 但是忙等 既要消耗 CPU 资源,又不执行任务,这样的设定是不科学的,所以我们就需要针对这样的忙等,进行进一步的优化

    出现忙等问题的代码   



    通过 wait() ,notify() 解决忙等问题    


当第一个 wait() 被触发,说明队列为空,只需要调用 schedule() 往队列中添加任务即可;

schedule() 里的操作恰好也需要锁,所以添加wait() notify() (都需要搭配锁来使用)来减轻程序忙等的现象,顺理成章;

因此唤醒第一个 wait() 的 notify() 在 schedule() 中设置;

    对于解决两处忙等问题的思路如下    


  • 第一个wait(),一被唤醒了,下面的逻辑被执行,是和当前 wait() 的判断条件(队列是否为空),是不冲突的;
  • 队列为空,只要时间到了,也能执行下面的逻辑,因此需要把 if() 改成 while(),让第一个 wait() 被唤醒后,再次判断是否满足唤醒条件;


  • 第二个 wait() 的唤醒条件,只看时间是否到了,到了才能执行下面的逻辑;
  • 所以第二个wait被打断了,else也会判断当前系统时间,是否已经到任务执行时间,因此不需要把 if() 改成while 

    补充    


单击其中一个 locker,其他 locker 同时显示,证明获取的 locker 为同一个;

虽然是在 lamda 中的对 this 加锁,看着像是 this 指向匿名内部类,然后对匿名内部类加锁,但是实际上是 lamda 的变量捕获,使得此处的this指向 MyTimer 这个外部类;


     2.3 模拟实现定时器完整代码    


package Thread;import java.util.PriorityQueue;
import java.util.TimerTask;
import java.util.concurrent.Executors;class MyTimerTask implements Comparable<MyTimerTask>{private Runnable task;//记录要执行的任务的时刻private long time;public MyTimerTask(Runnable task, long time) {this.task = task;this.time = time;}@Overridepublic int compareTo(MyTimerTask o) {return (int)(this.time-o.time);}public long getTime(){return time;}public void run(){task.run();}
}//模拟实现一个定时器
class MyTimer{private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();private Object locker = new Object();public void schedule(Runnable task , long delay){synchronized (locker){MyTimerTask timerTask = new MyTimerTask(task,System.currentTimeMillis()+delay);queue.offer(timerTask);locker.notify();}}public MyTimer(){//创建一个线程,负责执行队列中的任务Thread worker = new Thread(() -> {try {while (true){synchronized (locker){//取出队首元素while(queue.isEmpty()){locker.wait();}MyTimerTask task = queue.peek();if(System.currentTimeMillis()<task.getTime()){locker.wait(task.getTime() - System.currentTimeMillis());}else {task.run();queue.poll();}}}}catch (InterruptedException e) {throw new RuntimeException(e);}},"worker");}
}
public class Demo33 {public static void main(String[] args) {MyTimer timer = new MyTimer();timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hello 3000");}},3000);timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hello 2000");}},2000);timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hello 1000");}},1000);//Executors.newScheduledThreadPool(4);}}

     c96f743646e841f8bb30b2d242197f2f.gif

692a78aa0ec843629a817408c97a8b84.gif

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

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

相关文章

一文了解 inductive bias(归纳偏好)

&#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 归纳偏好&#xff08;Inductive Bias&#xff09;是机器学习中的一个非常基础但又非常重要的概念。为了更好地理解它&#xff0c;我们先从 “归纳” 和 “偏好” 这两个词开始讲解。 什么是归纳&#x…

【电子设计】按键LED控制与FreeRTOS

1. 安装Keilv5 打开野火资料,寻找软件包 解压后得到的信息 百度网盘 请输入提取码 提取码:gfpp 安装526或者533版本都可以 下载需要的 F1、F4、F7、H7 名字的 DFP pack 芯片包 安装完 keil 后直接双击安装 注册操作,解压注册文件夹后根据里面的图示步骤操作 打开说明 STM…

ROS Action

在 ROS 中&#xff0c;Action 是一种支持长时间异步任务的通信机制。与 Service 不同&#xff0c;Action 允许客户端发起一个请求&#xff0c;并在任务执行的过程中不断接收反馈&#xff0c;直到任务完成。这种机制非常适用于可能需要较长时间来完成的任务&#xff0c;比如机器…

Siglus引擎 Unpack | 未完待续

前言 未完待续。 代码在这里&#xff1a;https://github.com/N0zoM1z0/SiglusEngine-Extract 以后随时会更新。&#xff08;&#xff09; 因为我是选择直接逆向游戏引擎&#xff0c;在无源码&#xff0c;不hook的情况下硬逆Siglus…… 路漫漫。。。 read.sav 可以直接逆Sigl…

Windows docker下载minio出现“Using default tag: latestError response from daemon”

Windows docker下载minio出现 Using default tag: latest Error response from daemon: Get "https://registry-1.docker.io/v2/": context deadline exceeded 此类情况&#xff0c;一般为镜像地址问题。 {"registry-mirrors": ["https://docker.re…

ue中使用webui有效果白色拖动条 有白边

这种类型&#xff0c;分析发现跟ue没有关系 是网页代码的问题 可以在外头加个overflow: hidden; <body style"height: 100%; margin: 0;overflow: hidden;">完美解决

记录java Collections.sort踩的坑

前言 java Collections.sort 排序失效&#xff1f;为什么会排序失效呢&#xff1f; 需求和问题 需求&#xff1a;获取指定文件夹下的所有文件&#xff0c;并且按照修改时间顺序从大到小排序&#xff0c;如果修改时间相同&#xff0c;则按照创建时间从大到小排序 // 输入lis…

MODBUS TCP转CANOpen网关

Modbus TCP转CANopen网关 型号&#xff1a;SG-TCP-COE-210 产品用途 本网关可以实现将CANOpen接口设备连接到MODBUS TCP网络中&#xff1b;并且用户不需要了解具体的CANOpen和Modbus TCP 协议即可实现将CANOpen设备挂载到MODBUS TCP接口的 PLC上&#xff0c;并和CANOpen设备…

为何数据库推荐将IPv4地址存储为32位整数而非字符串?

目录 一、IPv4地址在数据库中的存储方式&#xff1f; 二、IPv4地址的存储方式比较 &#xff08;一&#xff09;字符串存储 vs 整数存储 &#xff08;二&#xff09;IPv4地址"192.168.1.8"说明 三、数据库推荐32位整数存储方式原理 四、存储方式对系统性能的影响…

《译文》2024年11月数维杯国际大学生数学建模挑战赛题目

# 赛题正式发布 2024年第十届数维杯国际大学生数学建模挑战赛顺利开赛&#xff0c;竞赛开始时间为北京时间2024年11月15日09:00至北京时间2024年11月19日09:00&#xff0c;共计4天&#xff0c;竞赛题目正式发布&#xff0c;快来一起围观&#xff0c;你认为今年的哪个题目更具有…

机器学习(贝叶斯算法,决策树)

朴素贝叶斯分类 贝叶斯分类理论 假设现有两个数据集&#xff0c;分为两类 我们现在用p1(x,y)表示数据点(x,y)属于类别1(图中红色圆点表示的类别)的概率&#xff0c;用p2(x,y)表示数据点(x,y)属于类别2(图中蓝色三角形表示的类别)的概率&#xff0c;那么对于一个新数据点(x,y)…

【会话文本nlp】对话文本解析库pyconverse使用教程版本报错、模型下载等问题解决超参数调试

前言&#xff1a; 此篇博客用于记录调用pyconverse库解析对话文本时遇到的问题与解决思路&#xff0c;以供大家参考。 文章目录 pycoverse介绍代码github链接问题解决1 [cannot import name ‘cached_download‘ from ‘huggingface_hub‘ 问题解决](https://blog.csdn.net/wei…

5G的SUCI、SUPI、5G-GUTI使用场景及关系

使用场景(来源于对23.501、23.502、33.501、23.003的理解) 1、UE初始注册时&#xff0c;根据HN Public Key把SUPI加密成SUCI&#xff0c;并发送初始注册请求 2、AMF转发SUCI给AUSF和UDM进行认证&#xff0c;并获取解密后的SUPI 3、AMF根据SUPI生成一个5G-GUTI&#xff0c;并保…

2. kafka 生产者

一. 生产者消息发送流程 在消息发送的过程中&#xff0c;涉及到了两个线程&#xff1a;main线程和Sender线程。Producer发送的消息会分别经过Interceptors(拦截器)&#xff0c;Serializer(序列化器)&#xff0c;Partitioner(分区器)最终到达RecordAccumulator&#xff0c;Recor…

web应用安全和信息泄露预防

文章目录 1&#xff1a;spring actuator导致的信息泄露1.1、Endpoint配置启用检测1.2、信息泄露复现1.3、防御 2&#xff1a;服务端口的合理使用3&#xff1a;弱口令&#xff08;密码&#xff09;管理4&#xff1a;服务端攻击4.1、短信业务&#xff0c;文件上传等资源型接口1、…

智慧安防丨以科技之力,筑起防范人贩的铜墙铁壁

近日&#xff0c;贵州省贵阳市中级人民法院对余华英拐卖儿童案做出了一审宣判&#xff0c;判处其死刑&#xff0c;剥夺政治权利终身&#xff0c;并处没收个人全部财产。这一判决不仅彰显了法律的威严&#xff0c;也再次唤起了社会对拐卖儿童犯罪的深切关注。 余华英自1993年至2…

计算机编程中的测试驱动开发(TDD)及其在提高代码质量中的应用

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 计算机编程中的测试驱动开发&#xff08;TDD&#xff09;及其在提高代码质量中的应用 计算机编程中的测试驱动开发&#xff08;T…

【MYSQL】锁详解(全局锁、表级锁、行级锁)【快速理解】

目录 一、全局锁 二、表级锁 1.表锁 2.元数据锁 3.意向锁 三、行级锁 1. 行锁 2.间隙锁 3.临建锁 锁是处理并发情况下&#xff0c;对数据的一致性的关键因素&#xff0c;也是并发情况下对效率影响非常大的。 1、全局锁&#xff1a;锁定表中所有数据。 2、表级锁&#xff1a;…

thinkphp6 入门(2)--视图、渲染html页面、赋值

use think\facade\View;View::assign([name > ThinkPHP,email > thinkphpqq.com]);View::assign(data,[name > ThinkPHP,email > thinkphpqq.com]); View::fetch(index);助手函数 view(index, [name > ThinkPHP,email > thinkphpqq.com ]); 模板输出 {$na…

百度智能云 VectorDB 优势数量 TOP 1

近日&#xff0c;IDC 发布了《RAG 与向量数据库市场前景预测》报告&#xff0c;深入剖析了检索增强生成&#xff08;RAG&#xff09;技术和向量数据库市场的发展趋势。报告不仅绘制了 RAG 技术的发展蓝图&#xff0c;还评估了市场上的主要厂商。在这一评估中&#xff0c;百度智…