【多线程】手把手带你学习定时器那些事

news2024/12/24 2:39:47

85bef0f9be7b443ebfd3dbba0de889c1.png

💐个人主页:初晴~

📚相关专栏:多线程 / javaEE初阶


        在软件开发中,有一些代码逻辑并不需要立马就被执行,可能需要等一段时间在执行。就好像我们会用闹钟来提醒我们过一段时间后要做某事一样,代码中也有“定时器”这种类似于闹钟的机制。那么,计时器都有什么特点,又该如何使用呢,就让博主手把手带你研究一下吧。

一、什么是定时器

定时器也是软件开发中的⼀个重要组件. 类似于⼀个 "闹钟".使其能够在未来的某个时间点或按照预定的时间间隔执行某个指定好的代码。是⼀种实际开发中⾮常常⽤的组件。

4658a6c4cf8b4267ae1e1b11d4118a15.png

常见作用:

1. 计划任务:定时器可以用于执行定期任务,比如数据备份、日志清理、定时发送邮件等。

2. 延迟操作:可以设置一个任务在一段时间后执行,例如,实现一个倒计时功能,在特定时间后触发事件。

3. 定时刷新:在Web应用中,定时器可以用于定时刷新页面或数据,保持与服务器同步的状态。

4. 心跳检测:在网络通信中,定时器可以用于定期发送心跳包,以保持连接活动状态,防止超时断开。

5. 自动更新:可以设置定时器来检查是否有软件更新,如果有的话就自动下载更新包。

6. 提醒服务:在个人应用中,如闹钟、日历应用等,定时器可以用来设置提醒。

7. 资源监控:定时器可以用于定期监控系统资源使用情况,如CPU、内存使用率,然后根据这些信息做出相应的资源管理决策。

8. 性能测试:在软件测试中,可以使用定时器来模拟用户行为,定期发送请求,从而测试系统的响应时间和负载能力。

9. 计时功能:在游戏开发或其他应用中,定时器可用于控制游戏时间或执行计时相关的逻辑。

10. 自动化脚本:定时器可以用于执行批处理脚本或自动化任务,比如定期清理临时文件、统计日志等。

11. 状态维护:某些网络协议要求客户端定期向服务器发送消息来维持连接状态,定时器可以帮助实现这种功能。

二、定时器的实现

实现要点:

1、创建类,描述要执行的任务是啥

2、管理多个任务,通过一定的数据结构,把多个任务存起来

3、用专门的线程去执行这些任务

1、描述任务

我们先创建一个MyTimerTask类,来负责描述所执行的任务

class MyTimerTask{
    private Runnable runnable;
    //通过毫秒时间戳,表示这个任务具体执行时间
    private long time;

    public MyTimerTask(Runnable runnable,long delay){
        this.runnable=runnable;
        this.time=System.currentTimeMillis()+delay;
    }

    public void run(){
        runnable.run();
    }
    public long getTime(){
        return time;
    }
}

注意:

1、通过runnable 成员变量来存储执行的任务

2、通过成员变量 time 来存储任务执行的时间。这里用毫秒时间戳来表示,方便程序进行判断

3、构造方法中的delay表示定时器所定时长,并将time赋值为系统当前毫秒时间戳与delay之和,从而让程序能更精准地确定任务所应执行的时刻。

2、管理任务

存储在定时器中的各个任务一定是执行时间越小越早执行。因此我们每次都会取出任务管理中 time 值最小的任务,将其与当前时间戳进行比较,如果小于等于当前时间戳则开始执行。

这样我们就是不断地在取出当前管理的时间戳最小的任务,显然用 优先级队列 来存储会比较合适。优先级队列就能保证每次取出的元素都是最小(大)的,且取出元素的效率比较高,因此我们可以初步实现一个MyTimer类来通过优先级队列来管理任务MyTimerTask:

class MyTimer{
    private PriorityQueue<MyTimerTask> queue=new PriorityQueue<>();
}

不过,PriorityQueue类会涉及到大小的比较,因此我们需要让MyTimerTask类重写Comparable接口:

8054e9c3f63c476587d10b3b7053ffe0.png

3、执行任务

在MyTimer内中实现不断取出堆顶元素并与当前时间戳比较,若当前时间戳以大于等于设定时间则执行,否则则不执行:

b7f9eb4367084d19af09e0c4d9584d31.png

再写一个schedule方法来负责往堆中加入新的元素:

f8e52774fb424df398ba3ac573efbadb.png

注意:由于这里涉及了大量的修改操作,为了避免线程安全问题,所以都为这两个操作加上了锁。对于线程安全问题的详细研究可以看一下博主的 深入剖析线程安全问题 一文

不过上述代码其实是存在很大问题的,比如:

(1)当队列中没有元素时:

b00a851d430a491d9c38da5b147450b4.png

该处的逻辑就会在短时间内进行大量地循环,会导致浪费许多系统资源来完成这种无意义的循环。类似于“线程饿死”的情况。

这时我们可以让线程在发现队列为空时就进入 wait 状态,等到调用schedule方法往队列中插入新元素时在调用notify让线程恢复运行即可。关于 wait 与notify的深入研究可以看看博主的 线程的等待与通知机制 。

d33fbd523f6b499f84b8688fa76f7996.png

a8071186f8ad4e66832615129a53dea4.png

(2)队列中已存在元素,但执行时间最小的任务与当前时间仍有一定时间间隔

ad804d9492a24bd7b6b90f327676d55c.png

比如当前时间为9:00,任务时间为11:00,这两个小时内程序就会不断进行上图的循环,也会浪费很多资源。同样的,我们也可以通过wait来解决这一问题:

222679fff45442c2951dc4bf3d718bd5.png

这里的wait与前面不同,不会指望schedule来唤醒。而是应该设置等待时间为任务执行时间与当前时间的差值,这样能保证刚好等待到最小执行任务需要执行的时间,然后重新恢复执行。

注意:这里的wait千万不能换成sleep

1、在等待的过程中,可能会调用 schedule 插入了一个执行时间更小的任务,如果是wait的话,就会被唤醒,重新设定等待时间。而sleep则会死等,中途插入新的任务可能就无法及时执行了

2、sleep休眠时不会释放锁,期间调用schedule方法就无法拿到锁,也就没有办法添加新的元素了

完整代码:

class MyTimerTask implements Comparable<MyTimerTask>{
    private Runnable runnable;
    //通过毫秒时间戳,表示这个任务具体执行时间
    private long time;

    public MyTimerTask(Runnable runnable,long delay){
        this.runnable=runnable;
        this.time=System.currentTimeMillis()+delay;
    }

    public void run(){
        runnable.run();
    }
    public long getTime(){
        return time;
    }
    @Override
    public int compareTo(MyTimerTask o) {
        //此处决定了是大堆还是小堆
        return (int) (this.time-o.time);
    }

}

class MyTimer{
    private PriorityQueue<MyTimerTask> queue=new PriorityQueue<>();
    public static Object lock=new Object();
    public MyTimer(){
        // 创建线程, 负责执行上述队列中的内容
        Thread t=new Thread(()->{
            while (true){
                synchronized (lock){
                    while(queue.isEmpty()){
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    MyTimerTask current=queue.peek();
                    if(System.currentTimeMillis()>=current.getTime()){
                        //执行任务
                        current.run();
                        //把执行过的任务从队列中删除
                        queue.poll();
                    }else {
                        //先不执行任务
                        try {
                            lock.wait(current.getTime()-System.currentTimeMillis());
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }
        });
        t.start();
    }
    public void schedule(Runnable runnable,long delay){
        synchronized (lock){
            MyTimerTask myTimerTask=new MyTimerTask(runnable,delay);
            queue.offer(myTimerTask);
            lock.notify();
        }
    }

}

三、java标准库中的定时器

标准库中提供了⼀个 Timer 类,可以用来调度定时任务
基本用法:
1、创建Timer实例:
Timer timer = new Timer();

2、创建TimerTask实例:

TimerTask 是一个抽象类,用户需要继承这个类并重写其中的 run() 方法来定义任务的具体行为。

class MyTask extends TimerTask {
    @Override
    public void run() {
        // 在这里写入任务代码
    }
}

3、调度任务:

  • 使用 schedule(TimerTask task, long delay) 方法来安排一个任务在指定的延迟后运行一次。
  • 使用 schedule(TimerTask task, long delay, long period) 方法来安排一个任务在首次延迟后运行,并且之后每隔一个固定的时间间隔重复执行。
Timer timer = new Timer();
MyTask myTask = new MyTask();
timer.schedule(myTask, 1000); // 延迟1秒后执行一次
timer.schedule(myTask, 1000, 60000); // 延迟1秒后开始,每分钟执行一次

4、取消任务:

如果不再需要执行某个任务,可以通过调用 TimerTaskcancel() 方法来取消它

myTask.cancel(); // 取消任务

5、停止Timer

如果要停止整个定时器,可以调用 Timercancel() 方法。

timer.cancel(); // 停止定时器

注意:

  • Timer 和 TimerTask 并不是线程安全的,如果多个线程同时访问同一个 Timer 或者 TimerTask 实例,可能会导致未定义的行为。
  • Timer 创建的线程默认是非守护线程,这意味着如果应用的其他所有非守护线程都结束了,JVM不会自动退出。如果你希望你的应用程序能够在所有工作线程结束后立即退出,你应该考虑将 Timer 设置为守护线程。
  • Timer 是单线程的,所以如果你有多个任务并且其中一个任务执行时间过长,那么它会阻塞其他的任务。

那么本篇文章就到此为止了,如果觉得这篇文章对你有帮助的话,可以点一下关注和点赞来支持作者哦。作者还是一个萌新,如果有什么讲的不对的地方欢迎在评论区指出,希望能够和你们一起进步✊

49b32cfb995a4f01a40b20f0b8a53579.png

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

裸土检测算法、裸土检测算法样本标注,裸土覆盖检测算法

裸土检测算法主要用于环境保护、土地管理和农业等领域&#xff0c;通过图像识别技术来检测地表上的裸露土壤区域。这种技术对于土地退化监测、水土流失预防、农田管理等方面有着重要意义。以下是关于裸土检测算法的技术实现、应用场景及优势的详细介绍。 应用场景 裸土检测算法…

kafka原理剖析及实战演练

一、消息系统概述 一&#xff09;消息系统按消息发送模型分类 1、peer-to-peer&#xff08;单播&#xff09; 特点&#xff1a; 一般基于pull或polling接收消息发送对队列中的消息被一个而且仅仅一个接收者所接收&#xff0c;即使有多个接收者在同一队列中侦听同一消息即支持异…

JVM - GC垃圾回收

文章目录 目录 文章目录 1. 自动垃圾回收 1.1 垃圾回收区域 2. 方法区回收 3. 堆回收 3.1 对象已死&#xff1f; 3.1.1 引用计数算法 3.1.2 可达性分析算法 3.1.3 再谈引用 强引用 软引用 弱引用 虚引用 3.2 垃圾收集算法 3.2.1 分代收集理论 3.2.2 垃圾回收算…

Android U 多任务启动分屏——Launcher流程(下分屏 更新中)

前文 Android U 多任务启动分屏——Launcher流程&#xff08;上分屏&#xff09; 最近任务onClick事件的监听 在最近任务中每个任务都是一个TaskView&#xff0c;对TaskView操作&#xff0c;就是每个任务的操作。 代码路径&#xff1a;packages/apps/Launcher3/quickstep/…

安装Anaconda(过程)

Anaconda是一个开源的Python发行版本&#xff0c;用来管理Python相关的包&#xff0c;安装Anaconda可以很方便的切换不同的环境&#xff0c;使用不同的深度学习框架开发项目&#xff0c;本文将详细介绍Anaconda的安装。 一、安装 1、安装方式 官网&#xff1a;“https://www.…

C#环境搭建和入门教程--vs2022之下

目录 1.环境搭建 2.先让程序跑起来 3.C#代码结构 4.变量&#xff0c;输入输出介绍 5.内容输入和类型转换 1.环境搭建 我们的这个c#基础学习主要就是在这个vs2022上面进行的&#xff0c;我们的这个c/c使用的都是这个平台 我们首先检查一下我们的这个环境是不是完全的配置了…

什么是API网关(API Gateway)?

1. 什么是API网关&#xff08;API Gateway&#xff09;&#xff1f; 在微服务体系结构中&#xff0c;客户端可能与多个前端服务进行交互。 API 网关位于客户端与服务之间。 它充当反向代理&#xff0c;将来自客户端的请求路由到服务。 它还可以执行各种横切任务&#xff0c;例…

技术美术一百问(01)

———————————————————问题篇———————————————————— 基础&#xff1a; 解释BRDF&#xff1f; 什么是Lightmap&#xff1f; 游戏里的各种液体怎么实现&#xff1f; 渲染流水线中&#xff0c;屏幕中的一个像素是怎么绘制出来的&#xff…

【推荐100个unity插件之33】比 Unity 自带协程更高效的异步处理方式,提供一个高性能和0GC的async/await异步方案——UniTask插件

文章目录 前言github地址GC&#xff08;Garbage Collection&#xff0c;垃圾回收&#xff09;GC 的影响 UniTask优缺点使用案例案例完结 前言 UniTask 是一个轻量级的异步编程库&#xff0c;专门为 Unity 设计&#xff0c;旨在提供比 Unity 自带协程更高效的异步处理方式。它是…

(11)(2.1.1) PWM、OneShot和OneShot125 ESC(一)

文章目录 前言 1 PWM 2 OneShot 3 参数说明 前言 大多数 ArduPilot 飞行器使用由无刷电机 ESC 控制的无刷电机。这些 ESC 使用的最常见协议是PWM、OneShot、OneShot125 和 DShot。本页介绍前三种&#xff08;PWM、OneShot 和OneShot125&#xff09;。 &#xff01;Warning…

中间件安全(一)

本文仅作为学习参考使用&#xff0c;本文作者对任何使用本文进行渗透攻击破坏不负任何责任。 一&#xff0c;中间件。 1&#xff0c;什么是中间件。 是一类能够为一种或多种应用程序合作互通、资源共享&#xff0c;同时还能够为该应用程序提供相关的服务的软件。中间件是一类…

哈尔滨的珍同学

写在前面 10225 字 | 朋友 | 旅行 | 友谊 | 情感 | 感触 | 思考 | 消极内容 全文篇幅过于庞大&#xff0c;请慎重考虑是否阅读。 与佳芯小姐的短文&#xff0c;将另行发布。 TL;DR 我不知道我和珍的关系还能维持多久。 按理来说&#xff0c;与朋友见面应当是一件开心的事情。这…

Linux系统连接蓝牙、WiFi方法分享,适用瑞芯微RK3562、RK3568、RK3588等开发板

本文适用于瑞芯微RK3562、RK3568、RK3588等各类开发板。本教程使用到的是深圳触觉智能开发的RK3562开发板&#xff0c;型号EVB3562&#xff0c;RK3562采用四核Cortex-A53 CPU&#xff0c;频率可达2.0GHz;最大支持 8GB 内存&#xff1b;内置独立的 NPU&#xff0c;可用于轻量级人…

卷轴模式系统源码开发:探索游戏世界——游戏模式的设计

在电子游戏的发展历程中&#xff0c;卷轴模式&#xff08;Scrolling Mode&#xff09;作为一种经典且广泛应用的游戏界面呈现方式&#xff0c;为玩家提供了沉浸式的探索体验。从早期的《超级马里奥兄弟renxb001》到现代的《塞尔达传说》系列&#xff0c;卷轴模式不仅定义了众多…

PPT中的图形与图片:插入、调整与格式设置技术详解

目录 引言 一、图形与图片的插入 1. 插入图形 2. 插入图片 二、图形与图片的调整 1. 调整大小与位置 2. 裁剪与旋转 3. 图形与图片的合并与组合 三、图片格式与布局设置 1. 图片格式设置 2. 图片布局设置 示例案例&#xff1a;制作产品展示PPT 四、结论 引言 在现…

Python中如何将图片资源打包进exe文件

目录 一、安装PyInstaller 二、准备图片资源 三、修改图片资源的引用方式 1. 使用Base64编码 2. 修改资源路径的引用 1. 打包命令 2. 打包后的文件 3. 运行exe文件 五、案例与测试 六、总结 在Python开发中,经常需要将图片等资源文件与Python脚本一起打包成独立的可…

【d41】【Java】【力扣】21.合并两个有序链表

题目 21. 合并两个有序链表 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&#xff1a; 输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;[1,1,2,3,4,4]示例 2&#xff1a; 输入&#xff1a;l1 [],…

【Python小知识 - 1】:pip下载离线包.whl

文章目录 .whl文件介绍一、批量下载1、requirements.txt准备2、下载相关包及依赖3、离线包下载 二、单个离线包下载1、下载相关包及依赖2、离线包下载 .whl文件介绍 .whl 文件是 Python 的一种打包格式&#xff0c;称为 Wheel。Wheel 是一种现代的 Python 包格式&#xff0c;旨…

由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(五)

概述 在 WWDC 24 中,苹果推出了数据库框架 SwiftData 2.0 版本。其新加入的历史记录追踪(History Trace)机制着实让秃头码农们“如痴如醉”了一番。 我们在之前的博文中已经介绍了 History Trace 是如何处理数据新增操作的。而在这里,我们将再接再厉来完成数据删除时的全盘…

OpenCV结构分析与形状描述符(19)查找二维点集的最小面积外接旋转矩形函数minAreaRect()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 找到一个包围输入的二维点集的最小面积旋转矩形。 该函数计算并返回指定点集的最小面积边界矩形&#xff08;可能是旋转的&#xff09;。开发者…