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


目录
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);}}

相关文章:
【JavaEE初阶 — 多线程】定时器的应用及模拟实现
目录 1. 标准库中的定时器 1.1 Timer 的定义 1.2 Timer 的原理 1.3 Timer 的使用 1.4 Timer 的弊端 1.5 ScheduledExecutorService 2. 模拟实现定时器 2.1 实现定时器的步骤 2.1.1 定义类描述任务 定义类描述任务 第一种定义方法 …...
Win10系统开启了文件夹管控(文件夹限制访问)导致软件向系统公共文档目录写入失败的问题排查分享
目录 1、问题说明 2、查看系统是否开启了文件夹管控 3、在未安装杀毒软件的Win10电脑上可能会自动打开文件夹管控 4、到微软官网上查看Windows 安全中心的病毒和威胁防护与文件夹管控的详细说明 5、解决办法探讨 6、最后 C++软件异常排查从入门到精通系列教程(专栏文章列…...
大数据的数据整合
数据整合是对导入的各类源数据进行整合,新进入的源数据匹配到平台上的标准数据,或者成为系统中新的标准数据。数据整合工具对数据关联关系进行设置。经过整合的源数据实现了基本信息的唯一性,同时又保留了与原始数据的关联性。具体功能包括关…...
回溯法经典难题解析
本文将通过几个经典的回溯问题,展示回溯算法的应用及其在解决问题时的核心思想和技巧。这些问题包括全排列、全排列II、N皇后以及数独问题,本文将分别介绍每个问题的思路与实现。 46. 全排列 给定一个不含重复数字的数组 nums ,返回其 所有…...
LLM的原理理解6-10:6、前馈步骤7、使用向量运算进行前馈网络的推理8、注意力层和前馈层有不同的功能9、语言模型的训练方式10、GPT-3的惊人性能
目录 LLM的原理理解6-10: 6、前馈步骤 7、使用向量运算进行前馈网络的推理 8、注意力层和前馈层有不同的功能 注意力:特征提取 前馈层:数据库 9、语言模型的训练方式 10、GPT-3的惊人性能 一个原因是规模 大模型GPT-1。它使用了768维的词向量,共有12层,总共有1.…...
Electron开发构建工具electron-vite(alex8088)添加VueDevTools(VitePlugin)
零、介绍 本文章的electron-vite指的是这个项目👉electron-vite仓库,electron-vite网站 本文章的VueDevTools指的是VueDevTools的Vite插件版👉https://devtools.vuejs.org/guide/vite-plugin 一、有一个用electron-vite创建的项目 略 二、…...
【C++】static修饰的“静态成员函数“--静态成员在哪定义?静态成员函数的作用?
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用 static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化 一、静态成员变量 1)特性 所有静态成员为所有类对象所共…...
=computed() =ref()
computed() ref() 在 Vue 中,computed() 和 ref() 是 Vue 3 组合式 API 的核心工具,它们分别用于 计算属性 和 响应式数据。以下是它们的区别和用法: 1. ref() 作用 用于创建响应式的单一数据。可以是基本类型(如字符串、数字、…...
webgl threejs 云渲染(服务器渲染、后端渲染)解决方案
云渲染和流式传输共享三维模型场景 1、本地无需高端GPU设备即可提供三维项目渲染 云渲染和云流化媒体都可以让3D模型共享变得简单便捷。配备强大GPU的远程服务器早就可以处理密集的处理工作,而专有应用程序,用户也可以从任何个人设备查看全保真模型并与…...
【shell编程】函数、正则表达式、文本处理工具
函数 系统函数 常见内置命令 echo打印输出 #!/bin/bash # 输出普通文本 echo "Hello, World!"# 输出变量值 name"Alice" echo "Hello, $name"# 输出带有换行符的文本 echo -n "Hello, " # -n 选项不输出换行 echo "World!&quo…...
解决 npm xxx was blocked, reason: xx bad guy, steal env and delete files
问题复现 今天一位朋友说,vue2的老项目安装不老依赖,报错内容如下: npm install 451 Unavailable For Legal Reasons - GET https://registry.npmmirror.com/vab-count - [UNAVAILABLE_FOR_LEGAL_REASONS] vab-count was blocked, reas…...
如何进行高级红队测试:OpenAI的实践与方法
随着人工智能(AI)技术的迅猛发展,AI模型的安全性和可靠性已经成为业界关注的核心问题之一。为了确保AI系统在实际应用中的安全性,红队测试作为一种有效的安全评估方法,得到了广泛应用。近日,OpenAI发布了两…...
Java:二维数组
目录 1. 二维数组的基础格式 1.1 二维数组变量的创建 —— 3种形式 1.2 二维数组的初始化 \1 动态初始化 \2 静态初始化 2. 二维数组的大小 和 内存分配 3. 二维数组的不规则初始化 4. 遍历二维数组 4.1 for循环 编辑 4.2 for-each循环 5. 二维数组 与 方法 5.1…...
Android 天气APP(三十七)新版AS编译、更新镜像源、仓库源、修复部分BUG
上一篇:Android 天气APP(三十六)运行到本地AS、更新项目版本依赖、去掉ButterKnife 新版AS编译、更新镜像源、仓库源、修复部分BUG 前言正文一、更新镜像源① 腾讯源③ 阿里源 二、更新仓库源三、修复城市重名BUG四、地图加载问题五、源码 前…...
Xilinx IP核(3)XADC IP核
文章目录 1. XADC介绍2.输入要求3.输出4.XADC IP核使用5.传送门 1. XADC介绍 xadc在 所有的7系列器件上都有支持,通过将高质量模拟模块与可编程逻辑的灵活性相结合,可以为各种应用打造定制的模拟接口,XADC 包括双 12 位、每秒 1 兆样本 (MSP…...
计算机网络socket编程(2)_UDP网络编程实现网络字典
个人主页:C忠实粉丝 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C忠实粉丝 原创 计算机网络socket编程(2)_UDP网络编程实现网络字典 收录于专栏【计算机网络】 本专栏旨在分享学习计算机网络的一点学习笔记,欢迎大家在评论区交流讨…...
c#窗体列表框(combobox)应用——省市区列表选择实例
效果如下: designer.cs代码如下: using System.Collections.Generic;namespace 删除 {public partial class 省市区选择{private Dictionary<string, List<string>> provinceCityDictionary;private Dictionary<string,List<string&…...
Nginx 架构与设计
Nginx 是一个高性能的 HTTP 和反向代理服务器,同时也可以用作邮件代理和通用的 TCP/UDP 负载均衡器。它的架构设计以高并发、高可扩展性和高性能为目标,充分利用操作系统提供的多路复用机制和事件驱动模型。以下是 Nginx 的架构和设计特点: 1…...
python Flask指定IP和端口
from flask import Flask, request import uuidimport json import osapp Flask(__name__)app.route(/) def hello_world():return Hello, World!if __name__ __main__:app.run(host0.0.0.0, port5000)...
多线程 相关面试集锦
什么是线程? 1、线程是操作系统能够进⾏运算调度的最⼩单位,它被包含在进程之中,是进程中的实际运作单位,可以使⽤多线程对 进⾏运算提速。 ⽐如,如果⼀个线程完成⼀个任务要100毫秒,那么⽤⼗个线程完成改…...
日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻
在如今就业市场竞争日益激烈的背景下,越来越多的求职者将目光投向了日本及中日双语岗位。但是,一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧?面对生疏的日语交流环境,即便提前恶补了…...
CTF show Web 红包题第六弹
提示 1.不是SQL注入 2.需要找关键源码 思路 进入页面发现是一个登录框,很难让人不联想到SQL注入,但提示都说了不是SQL注入,所以就不往这方面想了 先查看一下网页源码,发现一段JavaScript代码,有一个关键类ctfs…...
DockerHub与私有镜像仓库在容器化中的应用与管理
哈喽,大家好,我是左手python! Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库,用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...
STM32F4基本定时器使用和原理详解
STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序
一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...
12.找到字符串中所有字母异位词
🧠 题目解析 题目描述: 给定两个字符串 s 和 p,找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义: 若两个字符串包含的字符种类和出现次数完全相同,顺序无所谓,则互为…...
NLP学习路线图(二十三):长短期记忆网络(LSTM)
在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...
如何理解 IP 数据报中的 TTL?
目录 前言理解 前言 面试灵魂一问:说说对 IP 数据报中 TTL 的理解?我们都知道,IP 数据报由首部和数据两部分组成,首部又分为两部分:固定部分和可变部分,共占 20 字节,而即将讨论的 TTL 就位于首…...
return this;返回的是谁
一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请,不同级别的经理有不同的审批权限: // 抽象处理者:审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...
【分享】推荐一些办公小工具
1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由:大部分的转换软件需要收费,要么功能不齐全,而开会员又用不了几次浪费钱,借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...


