JavaEE 初阶(13)——多线程11之“定时器”
目录
一. 什么是“定时器”
二. 标准库的定时器
三. 定时器的实现 MyTimer
3.1 分析思路
1. 创建执行任务的类。
2. 管理任务
3. 执行任务
3.2 线程安全问题
四. 拓展
一. 什么是“定时器”
定时器是软件开发中的一个重要组件,类似于一个“闹钟”,达到一个设定的时间之后,就执行某个指定好的代码。
定时器是一种实际开发中非常常用的组件:比如网络通信中,如果对方 500ms 内没有返回数据,则断开连接尝试重连;比如一个 Map,希望里面的某个 key 在 3s 之后过期(自动删除)
类似于这样的场景就需要用到定时器。
二. 标准库的定时器
Timer类是一个线程安全的工具类,用于在指定的时间后执行或定期执行任务。它基于绝对时间,而不是基于固定时间间隔调度任务。Timer类比较简单,适用于简单的定时任务,但不适合处理复杂的调度需求。(ScheduleExecutorService 提供了更灵活的线程管理功能,适用于复杂的调度场景,并且能够处理异常)
- 标准库中(java.util.Timer ),Timer 类的核心方法为 schedule
- schedule 包含两个参数,第一个参数指定即将要执行的任务代码,第二个参数指定多长时间之后执行(单位为毫秒)
import java.util.Timer;
import java.util.TimerTask;public class Timer1 {public static void main(String[] args) throws InterruptedException {Timer timer1 = new Timer();timer1.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("hello delay"+" " +1000);}},1000);timer1.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("hello delay"+" " +2000);}},2000);timer1.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("hello delay"+" " +3000);}},3000);// 等待提交的任务执行完毕后,调用 cancel 便可结束进程Thread.sleep(4000);timer1.cancel();}
}
三. 定时器的实现 MyTimer
3.1 分析思路
对于定时器来说:
- 创建描述一个要执行的任务(任务内容 + 执行任务时间)的类
- 管理多个任务,通过一定的数据结构,把多个任务存起来
- 有专门的线程,执行这里的任务
1. 创建执行任务的类。
我们在调用 schedule 时,传的是延迟时间 “delay” 值。但是,描述任务时,不太建议使用 delay
表示,最好使用 “绝对时间”(时间戳)来表示~~
public class MyTimerTask implements Comparable<MyTimerTask>{//此处这里的 time,通过毫秒时间戳,表示这个任务具体啥时候执行private long time;private Runnable runnable;public MyTimerTask(Runnable runnable,long delay){this.time = System.currentTimeMillis() + delay;this.runnable = runnable;}public void run(){runnable.run();}public long getTime(){return time;}@Overridepublic int compareTo(MyTimerTask o) {//比如,当前时间是 10:30,任务时间是 12:00,不应该执行//如果当前时间是 10:30,任务时间是 10:29,应该执行//谁减去谁,可以通过实验判断return (int) (this.time - o.time);}
}
2. 管理任务
使用 List 管理任务,不是一个好选择——因为后续执行列表中的任务时,就需要依次遍历每个元素;执行完毕后,还需要把对应的任务从 List 中删除掉。
我们需要按照时间来执行这里的任务。只要能够确定所有任务中,时间最早的任务,判定它是否到该执行的时间即可。如果时间最早的任务还没到执行时间,其他任务更不可能到时间了。因此,我们使用堆数据结构(涉及到队列中的元素排序时,考虑堆)——PriorityQueue<MyTimerTask>(优先级队列管理元素时,需要有比较方法,才能排序存储。因此,在实现 MyTimerTask 类时,要继承 Comparable<MyTimerTask> 接口,重写 compareTo比较方法)
3. 执行任务
当创建 MyTimer 对象,调用无参构造方法时,便创建一个线程,循环执行从队列中取出任务的操作:取出队列中 “绝对时间” 最早的任务——如果当前时间 >= 此任务的时间(已经到达此任务的执行时间),便可调用run方法执行,执行完毕后从队列中删除; 如果当前时间 < 此任务的时间(没到此任务的执行时间),则继续执行循环。(所以,在实现 MyTimerTask 类时,要有 run方法 和 getTime方法)
除此之外 ,还需要有 schedule 方法添加任务。
import java.util.PriorityQueue;public class MyTimer {private final PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();public MyTimer() {Thread t = new Thread(() -> {while (true){if(queue.isEmpty()){ continue;}MyTimerTask task = queue.peek();//判断是否满足执行条件if (System.currentTimeMillis() >= task.getTime()) {task.run();//执行完后,便从队列中删除queue.poll();}else{continue;}}});t.start();}public void schedule(Runnable runnable, long delay) {MyTimerTask task = new MyTimerTask(runnable, delay);queue.offer(task);}
}
3.2 线程安全问题
当前这个代码,是没有考虑线程安全问题的。
PriorityQueue 这个类自身,是非线程安全的,并且又是多个线程来进行操作,一定存在线程安全问题的风险。因此,要在涉及队列相关操作的地方加锁。
import java.util.PriorityQueue;public class MyTimer {private final PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();private final Object locker = new Object();public MyTimer() {Thread t = new Thread(() -> {while (true){synchronized(locker){if(queue.isEmpty()){ continue;}MyTimerTask task = queue.peek();if (System.currentTimeMillis() >= task.getTime()) {task.run();queue.poll();}else{continue;}}}});t.start();}public void schedule(Runnable runnable, long delay) {synchronized(locker){MyTimerTask task = new MyTimerTask(runnable, delay);queue.offer(task);}}
}
但是,加完锁以后,又出现了线程安全问题。
1)初始情况下,如果队列中,没有任何元素。此时,就会在短时间内执行大量循环,这样的执行是没有意义的,导致“线程饿死”。
因此,我们需要添加 wait 和 notify 机制:队列为空时,进行等待;添加任务时,就唤醒线程。
2)假设队列中,已经包含元素了,并且当前时间是 10:45,任务时间 12:00(类似于,我定了12:00的闹钟,现在是 10:45)。在判断任务是否满足执行条件时,不满足就会一直循环(相当于每隔一会儿就看一眼闹钟),这样无意义的执行就一直占用着cpu资源,导致 “线程饿死”。
因此,我们需要添加一个有等待期限的 wait(等待 1h15min 就会执行),当到达任务执行时间,wait 就结束了。如果在等待过程中,又再次调用 schedule 方法,也会唤醒这里的 wait,进行新一轮的判断。
线程安全版:
import java.util.PriorityQueue;public class MyTimer {private final PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();private final Object locker = new Object();public MyTimer() {Thread t = new Thread(() -> {try {while (true) {synchronized (locker) {while (queue.isEmpty()) {//如果还没添加任务,会不断循环执行判断,出现线程饿死。//continue;//因此,使用wait等待,当添加任务后唤醒locker.wait();}MyTimerTask task = queue.peek();if (System.currentTimeMillis() >= task.getTime()) {task.run();queue.poll();} else {//如果还没到任务执行时间,依旧不断循环判断,出现线程饿死。//continue;//因此,使用有等待期限的 wait,计算执行的时间与当前时间的差值//当添加新的任务后,wait 被唤醒,再进行新的判断locker.wait(task.getTime() - System.currentTimeMillis());}}}} catch (InterruptedException e) {e.printStackTrace();}});t.start();}public void schedule(Runnable runnable, long delay) {synchronized (locker){MyTimerTask task = new MyTimerTask(runnable, delay);queue.offer(task);// 唤醒 waitlocker.notify();}}
}
* 能否将第二处的 wait 改为 sleep 呢? ——不能!!
不应该使用sleep,可能存在以下情况:
1)在 sleep 阻塞1h15min 的过程中,新来了一个时间更早的任务,比如 11:30 要执行。如果使用 wait ,每次新来的任务,都会把 wait 唤醒,重新设定 wait 的等待时间。而 sleep 不会被唤醒,依旧在阻塞着.....
2)sleep 休眠的时候,不会释放锁。因此,在休眠的时候就是“抱着锁”,其他人想拿锁就拿不到了。
* PriorityQueue 是线程不安全的类,能否使用 PriorityBlockingQueue 线程安全的阻塞队列呢?——不能!!
如果使用线程安全的队列,会导致代码中从 一把锁 变成 两把锁,很容易出现死锁的情况,比如持有和请求的情况(并非100%一定出现,但是需要程序员精心控制加锁顺序,使得编写代码的复杂度提高了。如果通篇代码 只有一把锁,就能更容易地解决问题)
* 如果某个任务的执行时间过长怎么办?
此处只有一个线程,串行执行每一个任务。如果某个任务执行时间过长,可能会影响到定时器的准确性(特别是对于周期性任务),使后续的任务被延迟——解决方案,多创建几个这样的工作线程。
四. 拓展
业界实现定时器,除了基于优先级队列的方式之外,还有一种经典的实现方式——“时间轮”
“时间轮” 是一种设计巧妙的数据结构,通常用于高效处理大量的定时任务。它是定时器任务调度的一种优化方案,尤其在需要调度大量短任务的场景中非常有效。时间轮的概念类似于钟表,它将时间划分为固定数量的槽位,每个槽位对应一个时间间隔,用于存储该时间间隔内需要执行的任务。
(这只是简单介绍,不做过多讨论,可自行查阅~~)
定时器特别重要,也特别常用,尤其是后端开发中,和 “阻塞队列” 类似,也会有专门服务器,用来在分布式系统中实现定时器这样的效果。
- hashmap --> redis
- 阻塞队列 --> 消息队列
- 定时器 --> 定时任务
相关文章:

JavaEE 初阶(13)——多线程11之“定时器”
目录 一. 什么是“定时器” 二. 标准库的定时器 三. 定时器的实现 MyTimer 3.1 分析思路 1. 创建执行任务的类。 2. 管理任务 3. 执行任务 3.2 线程安全问题 四. 拓展 一. 什么是“定时器” 定时器是软件开发中的一个重要组件,类似于一个“闹钟”࿰…...

2024最新全开源付费进群系统源码二开修复版 支持易支付
内容目录 一、详细介绍二、效果展示1.部分代码2.效果图展示 三、学习资料下载 一、详细介绍 全开源付费进群系统源码,开源无加密无授权,优化电脑端访问布局,支持dai理,对接易支付通道,dai理可以配置自己易支付接口&am…...

【奥顺苹果CMS二开泛目录4.X版】PHP站群程序新增首页堆砌关键词新增四套seo模板
演示站(赠送四套模板): https://macfan.qdwantong.com https://macfan2.qdwantong.com https://macfan3.qdwantong.com https://macfan4.qdwantong.com 4.X版程序特色功能: 后台除了可以设置干扰码、转码、插入符号和拼音这…...

day06 项目实践:router,axios
vue组件的生命周期钩子 今天几乎没有讲什么新内容,就是一起做项目,只有一个小小的知识点,就是关于vue组件的生命周期钩子,其中最重要的四个函数—— beforeCreate():组件创建之间执行 created():组件创建…...

⌈ 传知代码 ⌋ 基于矩阵乘积态的生成模型
💛前情提要💛 本文是传知代码平台中的相关前沿知识与技术的分享~ 接下来我们即将进入一个全新的空间,对技术有一个全新的视角~ 本文所涉及所有资源均在传知代码平台可获取 以下的内容一定会让你对AI 赋能时代有一个颠覆性的认识哦&#x…...

软件测试必备技能
在软件测试领域,以下是一些必备的技能和能力,可以帮助你成为一名优秀的软件测试工程师: 1. 测试基础知识: 熟悉软件测试的基本概念、原则和流程,包括不同类型的测试(如单元测试、集成测试、系统测试&#…...
TL3568编译uboot报错
编译uboot前,需要 ① sudo apt-get install device-tree-compiler 否则会报“ERROR: No dtc” ② sudo apt install python 装个Python2,否则会报“ERROR: No python2”...

qiankun 微前端 隔离子应用样式,解决 ant-design-vue 子应用样式污染问题(已落地)
样式冲突产生原因 先分析乾坤qiankun 构建之后,会根据你的配置 给每个子应用生成一个id, 当加载到对应子应用的时候,就把内容放到对应的id 标签里去, 这样能有效的隔离 js 代码,但是样式是加载在全局的 所以 当两个子…...

一个前后端分离架构的低代码开发平台,支持微服务架构,支持开发SAAS项目(附源码)
前言 在当前的企业软件开发领域,开发者常常面临着代码重复性高、开发效率低、项目周期长等挑战。现有的软件解-决方案往往难以满足快速变化的市场需求,特别是在SAAS项目、企业信息管理系统(MIS)、内部办公系统(OA&…...
whisper+whisperx ASR加对齐
忘了怎么安装了,这里记录一下整理出来的类,不过这个 from chj.comm.pic import *import json import whisper import whisperx import gcclass Warp_whisper:def __init__(self, language"zh", device"cuda", compute_type"fl…...

【已解决】YOLOv8加载模型报错:super().__init__(torch._C.PyTorchFileReader(name_or_buffer))
《博主简介》 小伙伴们好,我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源,可关注公-仲-hao:【阿旭算法与机器学习】,共同学习交流~ 👍感谢小伙伴们点赞、关注! 《------往期经典推…...

中国象棋 纯网页前端 演示与下载
https://andi.cn/app/chess/...
学习大数据DAY29 python基础语法2
目录 调试---debug tuple(元组) set(集合) dict(字典) 转换 推导式 上机练习 3 函数 参数 不定长参数 值传递与引用传递 局部和全局变量 上机练习 4 调试---debug 1. 先设置断点 2. 点击调试…...

自动化测试常用函数(Java方向)
目录 一、元素的定位 1.1 cssSelector 1.2 xpath 1.2.1 获取HTML页面所有的节点 1.2.2 获取HTML页面指定的节点 1.2.3 获取⼀个节点中的直接子节点 1.2.4 获取⼀个节点的父节点 1.2.5 实现节点属性的匹配 1.2.6 使用指定索引的方式获取对应的节点内容 二、操作测试对…...

申瓯通信设备有限公司在线录音管理系统(复现过程)
漏洞简介 申瓯通信设备有限公司在线录音管理系统 index.php接口处存在任意文件读取漏洞,恶意攻击者可能利用该漏洞读取服务器上的敏感文件,例如客户记录、财务数据或源代码,导致数据泄露 一.复现过程 fofa搜索语句:title"在线录音管…...

【C++进阶学习】第十一弹——C++11(上)——右值引用和移动语义
前言: 前面我们已经将C的重点语法讲的大差不差了,但是在C11版本之后,又出来了很多新的语法,其中有一些作用还是非常大的,今天我们就先来学习其中一个很重要的点——右值引用以及它所扩展的移动定义 目录 一、左值引用和…...
JavaScript 监听 localStorage 的变化
使用 JavaScript 监听 localStorage 的变化 在Web开发中,localStorage是一种非常常用的本地存储机制。它允许我们在浏览器中存储键值对数据,即使用户关闭了浏览器或刷新页面,数据也不会丢失。但是,有时我们需要实时监控 localStorage 的变化,以便能够及时做出响应。在本文中,我…...
Java 中 HashMap 和 Hashtable 的联系
目录 相同 不同 1. 继承的父类不同 2. 线程安全性不同 3. 包含的 contains 方法不同 4. toString方法不同 5. 是否允许null值不同 6. 计算hash值的方式不同 7. 计算索引位置的方法不同 8. 初始化容量不同 9. 扩容方式不同 10. 内部存储策略不同(此处讨论…...
Web3 开发教程
引言 Web3 是指第三代互联网,其核心特征之一是去中心化。通过区块链技术和智能合约,Web3 应用程序(dApps)能够在无需中心化服务器的情况下运行。本文将引导你完成一个简单的 Web3 应用程序的开发过程,包括环境搭建、智…...

傻瓜式PHP-Webshell免杀学习手册,零基础小白也能看懂
项目描述 一、PHP相关资料 PHP官方手册: https://www.php.net/manual/zh/ PHP函数参考: https://www.php.net/manual/zh/funcref.php 菜鸟教程: https://www.runoob.com/php/php-tutorial.html w3school: https://www.w3school…...

国防科技大学计算机基础课程笔记02信息编码
1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制,因此这个了16进制的数据既可以翻译成为这个机器码,也可以翻译成为这个国标码,所以这个时候很容易会出现这个歧义的情况; 因此,我们的这个国…...

Debian系统简介
目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版ÿ…...

Docker 运行 Kafka 带 SASL 认证教程
Docker 运行 Kafka 带 SASL 认证教程 Docker 运行 Kafka 带 SASL 认证教程一、说明二、环境准备三、编写 Docker Compose 和 jaas文件docker-compose.yml代码说明:server_jaas.conf 四、启动服务五、验证服务六、连接kafka服务七、总结 Docker 运行 Kafka 带 SASL 认…...

【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力
引言: 在人工智能快速发展的浪潮中,快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型(LLM)。该模型代表着该领域的重大突破,通过独特方式融合思考与非思考…...
linux 错误码总结
1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...

Map相关知识
数据结构 二叉树 二叉树,顾名思义,每个节点最多有两个“叉”,也就是两个子节点,分别是左子 节点和右子节点。不过,二叉树并不要求每个节点都有两个子节点,有的节点只 有左子节点,有的节点只有…...

智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制
在数字化浪潮席卷全球的今天,数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具,在大规模数据获取中发挥着关键作用。然而,传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时,常出现数据质…...

学校时钟系统,标准考场时钟系统,AI亮相2025高考,赛思时钟系统为教育公平筑起“精准防线”
2025年#高考 将在近日拉开帷幕,#AI 监考一度冲上热搜。当AI深度融入高考,#时间同步 不再是辅助功能,而是决定AI监考系统成败的“生命线”。 AI亮相2025高考,40种异常行为0.5秒精准识别 2025年高考即将拉开帷幕,江西、…...
在Ubuntu24上采用Wine打开SourceInsight
1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...