【多线程】Timer任务定时器实现与盲等原子性问题的解决
目录
一、定时器
二、标准库中的Timer
三、代码实现
四、死锁
一、定时器
代码中的定时器通常是在一定的时间执行对应的代码逻辑
二、标准库中的Timer
public static void main(String[] args){Timer timer = new Timer();timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("时间到了后执行业务逻辑");}},1000);}
一个timer可以执行多个定时任务,后续添加任务继续调用schedule方法即可
三、代码实现
首先我们定义一个类用于描述任务
// 用于描述任务
class MyTimerTask {// 执行的任务private Runnable runnable;// 什么时间后执行(绝对的时间)private long time;/**** @param runnable 任务* @param time 多少时间后执行*/public MyTimerTask(Runnable runnable, long time) {this.runnable = runnable;this.time = System.currentTimeMillis() + time;}
}
然后我们开始定时器的编写,首先我们需要一个数据结构来存储提交的定时任务,这个数据结构需要能够依次取出最先执行的任务且要是线程安全的,首先想到的是优先级队列其次要有阻塞功能就是阻塞队列,然后我们需要定义一个提交任务的方法该方法中可以将提交的任务存入该队列中,然后在构造方法中创建一个扫描线程不断地取出该队列里地任务进行执行。在此之前我们使用优先级队列是存储定时任务的,那么我们可以先给上面的类实现Compareable接口重写compareTo方法
class MyTimer {// 1. 创建存储定时任务的数据结构BlockingQueue<MyTimerTask> queue = new PriorityBlockingQueue<>();// 2. 定义提交定时任务的方法public void schedule(Runnable runnable,long after) throws InterruptedException {MyTimerTask task = new MyTimerTask(runnable,after);queue.put(task);}// 3. 构造方法中定义扫描线程public MyTimer() {new Thread(()->{// 3.1 不断的取出数据看是否需要执行while (true) {try {// 3.1.1 拿出最先需要执行的任务判断是否到达执行时间MyTimerTask task = queue.take();if (System.currentTimeMillis() >= task.getTime()) {// 3.1.2 到达时间执行任务task.getRunnable().run();} else {// 3.1.3 没到时间重回队列queue.put(task);}} catch (InterruptedException e) {e.printStackTrace();}}}).start();}
}
这个时候盲等问题就出现了,比如我们提交了一个2小时后才执行的任务,但是按照上述代码则在这2小时的时间里不断地从队列中取出该任务比较后重回队列,那可以使用sleep(2h)这种方法来解决问题吗?答案是不能,使用sleep方法让线程挂起两个小时可以保证2小时后的任务会被执行,但是如果中途有其他更早的任务提交进来,那么这个任务就会错过执行的时间。那让每次sleep的时间短一点呢?答案同理也是不能的。我们可以使用wait方法来实现,wait(2h)然后在提交任务的方法中一但有新的任务提交调用notify唤醒wait即可,如果在这两个小时内没有任务提交,那么该方法还是会在2h后去执行任务。
// 2. 定义提交定时任务的方法public void schedule(Runnable runnable,long after) throws InterruptedException {MyTimerTask task = new MyTimerTask(runnable,after);queue.put(task);synchronized (this) {this.notify();}}// 3. 构造方法中定义扫描线程public MyTimer() {new Thread(()->{// 3.1 不断的取出数据看是否需要执行while (true) {try {// 3.1.1 拿出最先需要执行的任务判断是否到达执行时间MyTimerTask task = queue.take();if (System.currentTimeMillis() >= task.getTime()) {// 3.1.2 到达时间执行任务task.getRunnable().run();} else {// 3.1.3 没到时间重回队列queue.put(task);// 3.1.4 阻塞synchronized (this) {this.wait(task.getTime() - System.currentTimeMillis());}}} catch (InterruptedException e) {e.printStackTrace();}}}).start();}
这样我们就解决了盲等问题,但是还有一个原子性问题,就是如果此时扫描线程刚好取出了最先要执行的任务该任务是在2h后执行,扫描线程在判断是否到达执行时间之前,其他线程调用添加任务的方法加入了一个1h后需要执行的任务且方法执行完notify没有起到任何作用,此时扫描线程判断完后发现还没有到时间于是将任务入队后调用wait方法等待2h或被唤醒,刚好这2h没有其他任务加入,那么之前提交的1h后的任务就会延迟执行。这是由于扫描线程中操作不是原子性的我们需要调整锁的粒度
new Thread(()->{// 3.1 不断的取出数据看是否需要执行while (true) {try {synchronized (this) {// 3.1.1 拿出最先需要执行的任务判断是否到达执行时间MyTimerTask task = queue.take();if (System.currentTimeMillis() >= task.getTime()) {// 3.1.2 到达时间执行任务task.getRunnable().run();} else {// 3.1.3 没到时间重回队列queue.put(task);// 3.1.4 阻塞this.wait(task.getTime() - System.currentTimeMillis());}}} catch (InterruptedException e) {e.printStackTrace();}}}).start()
那么notify代码中的锁粒度是否也需要调整呢?
四、死锁
如果我们将schedule方法中锁的粒度也扩大
public void schedule(Runnable runnable,long after) throws InterruptedException {synchronized (this) {MyTimerTask task = new MyTimerTask(runnable,after);queue.put(task);this.notify();}}
这个时候我们进行测试会发现什么也不会执行,发送了死锁。那这是为什么呢?
首先MyTimer实例被创建时扫描线程开始执行当他执行到此处时会因为阻塞队列中还没有元素而阻塞等待
但是锁还是被持有,此时提交任务的代码执行时发现需要先获取到锁,但是锁是被扫描线程持有,于是他需要阻塞等待,但是扫描线程中的take方法也需要执行了提交任务方法中的put才能继续执行,但是执行put方法有需要扫描线程先释放锁,所以发生死锁,这个时候我们需要将schedule方法中锁的粒度修改回去
相关文章:

【多线程】Timer任务定时器实现与盲等原子性问题的解决
目录 一、定时器 二、标准库中的Timer 三、代码实现 四、死锁 一、定时器 代码中的定时器通常是在一定的时间执行对应的代码逻辑 二、标准库中的Timer public static void main(String[] args){Timer timer new Timer();timer.schedule(new TimerTask() {Overridepublic…...

SpringCloud-GetWay 路由网关
接上文 SpringCloud-Hystrix 服务降级与熔断 微服务也是如此,不是所有微服务需要直接暴露给外部调用,就需要使用路由机制,添加一层防护,让所有的请求全部通过路由来转发到各个微服务,并转发给多个相同微服务实例&#…...

使用生成式 AI 增强亚马逊云科技智能文档处理
数据分类、提取和分析对于处理大量文档的组织来说可能具有挑战性。传统的文档处理解决方案是手动的、昂贵的、容易出错的,并且难以扩展。利用 Amazon Textract 等 AI 服务,亚马逊云科技智能文档处理(IDP)允许您利用业界领先的机器学习(ML)技术来快速准确地处理任何扫描文档或图…...

谈论浏览器内核
浏览器内核是指浏览器使用的渲染引擎,用于解析并显示网页的内容。主要有以下几种浏览器内核: Trident(IE内核):由Microsoft开发,被用于Internet Explorer浏览器。目前已经被Edge取代。 Gecko:…...
电商卖家保障数据隐私和安全用什么安全的浏览器?
在如今信息爆炸的时代,个人数据安全成为了一个备受关注的话题。越来越多的人意识到,保护个人数据的重要性。为此,安全浏览器应运而生,为用户提供更加安全可靠的上网环境,保障个人数据的安全。 一、数据安全的重要性 …...

ECS通过DNAT将C非专线网段并网
1.问题描述 客户需求:ECS1需要访问140.131.208.0/24 ,由于140.131.208.0/24网段属于公网地址,在CSW侧为进行并网。 解决方案:故将ECS1发起的请求其在云内ECS2做DNAT,将该网段转换成CSW并网网段170.101.253.0/24&…...
g++模板显式实例化big file例子
前言 模板是编程中高级工具,类似C语言的宏生成代码,但却比宏更强大,例如,对于调试的支持,以及实现更严格的语法检查。 如果用节省代码来定义工具的好坏,无疑不管用C语言宏来生成代码,或者用C的…...

Redis 删除策略
文章目录 Redis 删除策略一、过期数据二、数据删除策略1、定时删除2、惰性删除3、定期删除4、删除策略对比 三、逐出算法 Redis 删除策略 一、过期数据 Redis是一种内存级数据库,所有数据均存放在内存中,内存中的数据可以通过TTL指令获取其状态 XX &a…...

自动化运维——ansible (五十二) (01)
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 目录 一、概述 1.1 为什么要用自动化运维软件 1.2 自动化运维 1.3 自动化运维要注意的方面 1.4 自动化运维主要关注的方面 1.5 常见的开源自动化运维软件 1.6 自动化运维软件…...

渗透测试漏洞原理之---【不安全的反序列化】
文章目录 1、序列化与反序列化1.1、引入1.2、序列化实例1.2.1、定义一个类1.2.2、创建 对象1.2.3、反序列化1.2.4、对象注入 2、漏洞何在2.1、漏洞触发2.1.2、定义一个类2.1.3、定义一个对象2.1.3、反序列化执行代码 2.2 为什么会这样 3、反序列化漏洞攻防3.1、PHP反序列化实例…...
建站系列(四)--- Web服务器之Apache、Nginx
目录 相关系列文章前言一、简介二、Apache与Nginx(1)Apache与Nginx的区别(2)Nginx相对于Apache的优点(3)Apache相对于Nginx 的优点(4)选择 三、反向代理与正向代理 相关系列文章 建…...
TCP和UDP的区别
TCP和UDP的区别 1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接 2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复&…...
MBR、GPT、LVM分区
GPT分区(支持大于2T的空间分区UEFI系统) 支持128个主分区 (parted) mklabel New disk label type? gpt (parted) mkpart Partition name? []? vdb1 File system type? [ext2]? ext4 Start? 0% End? 40% (parted) mkpart Partition name? []…...
uniapp 下拉刷新
需求:我使用一个滚动列表,需要下拉刷新页面的功能 下拉刷新的情况取决于滚动列表使用的技术 第一 种:页面滚动 产生页面很简单,只需要列表长度超过页面高度,就直接产生了滚动条。 处理页面滚动的下拉刷新。 1. 配置…...
ifstream之seekg/tellg
声明:我个人特别讨厌:收费专栏、关注博主才可阅读等行为,推崇知识自由分享,推崇开源精神,呼吁你一起加入,大家共同成长进步! 在文件读写的时候,一般需要借助fstream来进行文件操作&a…...

OpenCV 01(图像加载与显示)
一、机器视觉 现在说的机器视觉(Machine Vision)一般指计算机视觉(Computer Vision), 简单来说就是研究如何使机器看懂东西。就是是指用摄影机和电脑代替人眼对目标进行识别、跟踪和测量等机器视觉,并进一步做图形处理,使电脑处理成为更适合人眼观察或传…...
1-Pytorch初始化张量和张量的类型
1-Pytorch初始化张量和张量的类型 1 导入必备库 import torch import numpy as np2 初始化张量 # 初始化张量 t torch.tensor([1,2])#.type(torch.FloatTensor) print(t) print(t.dtype)输出: tensor([1, 2]) torch.int643 创建float型张量 # 创建float型张量…...
诊断网络卡的原因
首先,通过ipconfig和ping命令来诊断。 手头要有一台Windows电脑。在dos窗口下,输入ipconfig,可以查看到本机“手动设置”或者“自动获取”的IP地址。 这里有几种可能性: IP地址和网关地址都正确。(不存在问题…...

100万级连接,爱奇艺WebSocket网关如何架构
说在前面 在40岁老架构师 尼恩的读者社区(50)中,很多小伙伴拿到一线互联网企业如阿里、网易、有赞、希音、百度、滴滴的面试资格。 最近,尼恩指导一个小伙伴简历,写了一个《高并发网关项目》,此项目帮这个小伙拿到 字节/阿里/微…...

当电脑遇到msvcp110.dll丢失怎么办?最新解决方法分享
在使用电脑过程中,我们经常会遇到一些系统文件丢失的问题。其中,msvcp110.dll是Windows操作系统中的一个重要的动态链接库文件,它包含了许多与C运行库有关的函数和类。当系统中缺少或损坏这个文件时,可能会导致一些应用程序无法正…...

SpringBoot-17-MyBatis动态SQL标签之常用标签
文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...

RocketMQ延迟消息机制
两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数,对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后…...

SCAU期末笔记 - 数据分析与数据挖掘题库解析
这门怎么题库答案不全啊日 来简单学一下子来 一、选择题(可多选) 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘:专注于发现数据中…...

基于当前项目通过npm包形式暴露公共组件
1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹,并新增内容 3.创建package文件夹...

Cinnamon修改面板小工具图标
Cinnamon开始菜单-CSDN博客 设置模块都是做好的,比GNOME简单得多! 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...

AI,如何重构理解、匹配与决策?
AI 时代,我们如何理解消费? 作者|王彬 封面|Unplash 人们通过信息理解世界。 曾几何时,PC 与移动互联网重塑了人们的购物路径:信息变得唾手可得,商品决策变得高度依赖内容。 但 AI 时代的来…...

【笔记】WSL 中 Rust 安装与测试完整记录
#工作记录 WSL 中 Rust 安装与测试完整记录 1. 运行环境 系统:Ubuntu 24.04 LTS (WSL2)架构:x86_64 (GNU/Linux)Rust 版本:rustc 1.87.0 (2025-05-09)Cargo 版本:cargo 1.87.0 (2025-05-06) 2. 安装 Rust 2.1 使用 Rust 官方安…...
C++.OpenGL (20/64)混合(Blending)
混合(Blending) 透明效果核心原理 #mermaid-svg-SWG0UzVfJms7Sm3e {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-SWG0UzVfJms7Sm3e .error-icon{fill:#552222;}#mermaid-svg-SWG0UzVfJms7Sm3e .error-text{fill…...
Python实现简单音频数据压缩与解压算法
Python实现简单音频数据压缩与解压算法 引言 在音频数据处理中,压缩算法是降低存储成本和传输效率的关键技术。Python作为一门灵活且功能强大的编程语言,提供了丰富的库和工具来实现音频数据的压缩与解压。本文将通过一个简单的音频数据压缩与解压算法…...
Oracle实用参考(13)——Oracle for Linux物理DG环境搭建(2)
13.2. Oracle for Linux物理DG环境搭建 Oracle 数据库的DataGuard技术方案,业界也称为DG,其在数据库高可用、容灾及负载分离等方面,都有着非常广泛的应用,对此,前面相关章节已做过较为详尽的讲解,此处不再赘述。 需要说明的是, DG方案又分为物理DG和逻辑DG,两者的搭建…...