当前位置: 首页 > news >正文

【多线程】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 服务降级与熔断 微服务也是如此&#xff0c;不是所有微服务需要直接暴露给外部调用&#xff0c;就需要使用路由机制&#xff0c;添加一层防护&#xff0c;让所有的请求全部通过路由来转发到各个微服务&#xff0c;并转发给多个相同微服务实例&#…...

使用生成式 AI 增强亚马逊云科技智能文档处理

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

谈论浏览器内核

浏览器内核是指浏览器使用的渲染引擎&#xff0c;用于解析并显示网页的内容。主要有以下几种浏览器内核&#xff1a; Trident&#xff08;IE内核&#xff09;&#xff1a;由Microsoft开发&#xff0c;被用于Internet Explorer浏览器。目前已经被Edge取代。 Gecko&#xff1a;…...

电商卖家保障数据隐私和安全用什么安全的浏览器?

在如今信息爆炸的时代&#xff0c;个人数据安全成为了一个备受关注的话题。越来越多的人意识到&#xff0c;保护个人数据的重要性。为此&#xff0c;安全浏览器应运而生&#xff0c;为用户提供更加安全可靠的上网环境&#xff0c;保障个人数据的安全。 一、数据安全的重要性 …...

ECS通过DNAT将C非专线网段并网

1.问题描述 客户需求&#xff1a;ECS1需要访问140.131.208.0/24 &#xff0c;由于140.131.208.0/24网段属于公网地址&#xff0c;在CSW侧为进行并网。 解决方案&#xff1a;故将ECS1发起的请求其在云内ECS2做DNAT&#xff0c;将该网段转换成CSW并网网段170.101.253.0/24&…...

g++模板显式实例化big file例子

前言 模板是编程中高级工具&#xff0c;类似C语言的宏生成代码&#xff0c;但却比宏更强大&#xff0c;例如&#xff0c;对于调试的支持&#xff0c;以及实现更严格的语法检查。 如果用节省代码来定义工具的好坏&#xff0c;无疑不管用C语言宏来生成代码&#xff0c;或者用C的…...

Redis 删除策略

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

自动化运维——ansible (五十二) (01)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 一、概述 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&#xff08;1&#xff09;Apache与Nginx的区别&#xff08;2&#xff09;Nginx相对于Apache的优点&#xff08;3&#xff09;Apache相对于Nginx 的优点&#xff08;4&#xff09;选择 三、反向代理与正向代理 相关系列文章 建…...

TCP和UDP的区别

TCP和UDP的区别 1、TCP面向连接&#xff08;如打电话要先拨号建立连接&#xff09;;UDP是无连接的&#xff0c;即发送数据之前不需要建立连接 2、TCP提供可靠的服务。也就是说&#xff0c;通过TCP连接传送的数据&#xff0c;无差错&#xff0c;不丢失&#xff0c;不重复&…...

MBR、GPT、LVM分区

GPT分区&#xff08;支持大于2T的空间分区UEFI系统&#xff09; 支持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 下拉刷新

需求&#xff1a;我使用一个滚动列表&#xff0c;需要下拉刷新页面的功能 下拉刷新的情况取决于滚动列表使用的技术 第一 种&#xff1a;页面滚动 产生页面很简单&#xff0c;只需要列表长度超过页面高度&#xff0c;就直接产生了滚动条。 处理页面滚动的下拉刷新。 1. 配置…...

ifstream之seekg/tellg

声明&#xff1a;我个人特别讨厌&#xff1a;收费专栏、关注博主才可阅读等行为&#xff0c;推崇知识自由分享&#xff0c;推崇开源精神&#xff0c;呼吁你一起加入&#xff0c;大家共同成长进步&#xff01; 在文件读写的时候&#xff0c;一般需要借助fstream来进行文件操作&a…...

OpenCV 01(图像加载与显示)

一、机器视觉 现在说的机器视觉(Machine Vision)一般指计算机视觉(Computer Vision), 简单来说就是研究如何使机器看懂东西。就是是指用摄影机和电脑代替人眼对目标进行识别、跟踪和测量等机器视觉&#xff0c;并进一步做图形处理&#xff0c;使电脑处理成为更适合人眼观察或传…...

1-Pytorch初始化张量和张量的类型

1-Pytorch初始化张量和张量的类型 1 导入必备库 import torch import numpy as np2 初始化张量 # 初始化张量 t torch.tensor([1,2])#.type(torch.FloatTensor) print(t) print(t.dtype)输出&#xff1a; tensor([1, 2]) torch.int643 创建float型张量 # 创建float型张量…...

诊断网络卡的原因

首先&#xff0c;通过ipconfig和ping命令来诊断。 手头要有一台Windows电脑。在dos窗口下&#xff0c;输入ipconfig&#xff0c;可以查看到本机“手动设置”或者“自动获取”的IP地址。 这里有几种可能性&#xff1a; IP地址和网关地址都正确。&#xff08;不存在问题&#xf…...

100万级连接,爱奇艺WebSocket网关如何架构

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

当电脑遇到msvcp110.dll丢失怎么办?最新解决方法分享

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

5个关键技巧:掌握AutoJs6界面布局设计的最佳实践

5个关键技巧&#xff1a;掌握AutoJs6界面布局设计的最佳实践 【免费下载链接】AutoJs6 安卓平台 JavaScript 自动化工具 (Auto.js 二次开发项目) 项目地址: https://gitcode.com/gh_mirrors/au/AutoJs6 AutoJs6作为安卓平台领先的JavaScript自动化工具&#xff0c;其界面…...

从DP-V0到DP-V2:一文讲透Profibus-DP三大版本的核心差异与工业现场选型建议

从DP-V0到DP-V2&#xff1a;Profibus-DP三大版本的核心差异与工业现场选型指南 在工业自动化领域&#xff0c;实时通信协议的选型往往直接决定生产线的响应速度、诊断能力和系统扩展性。作为制造业自动化系统中应用最广泛的现场总线之一&#xff0c;Profibus-DP历经三次重大版本…...

Cursor Pro破解工具终极指南:5步实现永久免费使用的完整教程

Cursor Pro破解工具终极指南&#xff1a;5步实现永久免费使用的完整教程 【免费下载链接】cursor-free-vip [Support 0.45]&#xff08;Multi Language 多语言&#xff09;自动注册 Cursor Ai &#xff0c;自动重置机器ID &#xff0c; 免费升级使用Pro 功能: Youve reached yo…...

NetApp FAS FC SAN存储替换实战:从HP MSA到ONTAP的平滑迁移

1. 项目背景与环境摸底 这次遇到的存储替换项目挺典型的——客户原先用的是HP MSA系列SAN存储&#xff0c;现在要升级到NetApp FAS2750全闪存阵列。现场环境是标准的VMware虚拟化平台&#xff0c;通过FC协议连接存储。说实话&#xff0c;第一次看到旧存储配置时我就发现几个隐患…...

Matlab ode45求解微分方程保姆级教程:从单变量到多智能体系统,附完整代码

Matlab ode45求解微分方程&#xff1a;从单变量到多智能体系统的工程实践 微分方程是描述动态系统演化的核心数学工具&#xff0c;而Matlab的ode45求解器则是工程师和科研人员最常用的数值求解利器。本文将带你从最基础的单个微分方程求解出发&#xff0c;逐步深入到多智能体系…...

UWB车内目标探测技术【附仿真】

✨ 长期致力于UWB雷达、活体、目标检测、生命体征、信号模型研究工作&#xff0c;擅长数据搜集与处理、建模仿真、程序编写、仿真设计。 ✅ 专业定制毕设、代码 ✅ 如需沟通交流&#xff0c;点击《获取方式》 &#xff08;1&#xff09;UWB雷达生命体征信号建模与自适应杂波抑制…...

通过稳定的路由与容灾机制保障关键业务中的AI服务连续性

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 通过稳定的路由与容灾机制保障关键业务中的AI服务连续性 在将大模型能力集成到关键业务流程时&#xff0c;服务的连续性与可靠性是…...

mmdetection环境搭建避坑指南:从CUDA版本、pip源到Gitee镜像的全流程优化

MMDetection环境搭建全流程优化&#xff1a;从版本匹配到镜像加速的实战指南 在计算机视觉领域&#xff0c;OpenMMLab系列工具包已经成为许多研究者和开发者的首选。作为其中的核心检测库&#xff0c;MMDetection凭借其模块化设计和丰富的预训练模型&#xff0c;极大地简化了目…...

3分钟搞定!VideoDownloadHelper视频下载插件终极安装使用指南

3分钟搞定&#xff01;VideoDownloadHelper视频下载插件终极安装使用指南 【免费下载链接】VideoDownloadHelper Chrome Extension to Help Download Video for Some Video Sites. 项目地址: https://gitcode.com/gh_mirrors/vi/VideoDownloadHelper 还在为无法保存网页…...

别再让电机烧了!聊聊工业设备中三相电源保护的两种经典电路设计与选型

工业三相电机保护电路设计实战&#xff1a;从原理到工程落地 在空压机房嘈杂的轰鸣声中&#xff0c;老王师傅正对着烧毁的电机摇头叹气——这已经是本月第三台因电源故障报废的设备。类似场景在工业现场屡见不鲜&#xff0c;统计显示超过40%的电机故障源于电源异常&#xff0c;…...