[线程] 定时器 及 实现定时器

news2024/9/21 22:36:33

文章目录

  • 一. 定时器
  • 二. 标准库中的定时器
  • 三. 实现定时器
    • 1. 创建MyTimerTask类
    • 2. 通过一定的数据结构, 保存多个任务
    • 3.MyTimer类的构造方法
    • 4. 实现schedule方法
    • 完整代码:

一. 定时器

定时器, 就是"闹钟"的效果
指定一个任务(Runnable), 并且指定一个时间, 此时这个任务不会立即执行, 而是在时间到达后, 再去执行
在日常开发中, 定时器是一个非常重要的基础组件, 甚至会把定时器功能单独封装成服务器, 供整个分布式系统来使用

二. 标准库中的定时器

标准库中提供了一个 Timer 类. Timer 类的核心方法为 schedule .
schedule 包含两个参数. 第一个参数指定即将要执行的任务代码, TimerTask类型(可以当成Runnable使用), 第二个参数delay指定多长时间之后执行 (单位为毫秒)

 public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello 3000");
            }
        },3000);
        //可以设多个定时器
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello 2000");
            }
        },2000);

    }

Timer内部包含了线程, 把线程创建封装起来了, 并且是前台线程, 阻止进程的结束
在这里插入图片描述

三. 实现定时器

1. 创建MyTimerTask类

需要包括执行的任务, 和执行任务的绝对时间(时间戳)

为什么用绝对时间,而不是用delay
为了方便后续执行任务的时候, 可以方便判定, 该任务是否能够执行
如果是delay, 随着时间的推移, 还需要更新delay, 比较麻烦

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 this.time;
    }
}

2. 通过一定的数据结构, 保存多个任务

如果我们使用ArrayList
因为我们要保证, delay时间短的先执行, 那么我们就需要不停地循环扫描ArrayList这里的每个任务, 判定是否要执行
更好的办法就是使用优先级队列
把这些任务通过优先级队列保存起来, 按照时间作为优先级的先后标准, 就可以做到, 队首元素就是时间最靠前的任务, 那么执行任务的时候, 只需要取队首元素即可
在这里插入图片描述
那么, 使用优先级队列, 就需要里面的对象实现Comparable接口, 那么MyTimerTask就需要修改一下:

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 this.time;
    }

    @Override
    public int compareTo(MyTimerTask o) {
        return (int)(this.getTime() - o.getTime());
        //return (int)(o.getTime() - this.getTime());
    }
}

因为我们要实现的是小根堆, 在compareTo方法中, 是 谁 - 谁, 我们不要去背, 随便写一个试试就知道对不对了, 背很容易背错!!!

3.MyTimer类的构造方法

在MyTimer类的构造方法中, 创建一个线程, 用这个线程来执行队列中的任务

public MyTimer(MyTimerTask myTimerTask, long delay){
        Thread thread = new Thread(() -> {
            while(true){
                //如果任务队列为空,就重新判断是否要执行
                if(queue.size() == 0){
                    continue;
                }
                MyTimerTask task = queue.peek();
                //如果此时的时间 >= 要执行的任务的时间, 则执行任务, 并出队列, 反之则重新判断
                if(task.getTime() >= System.currentTimeMillis()){
                    task.run();
                    queue.poll();
                }else{
                    continue;
                }
            }
        });
        thread.start();
    }

改进:

  1. 上述代码中, 如果任务队列为空或者还没到执行的时间, 就会处于忙等的状态, 短时间内循环很多次, 直到队列不为空或等到执行时间, 会一直消耗cpu资源, 但是并没有执行真正的任务, 所以我们要让线程处于等待的时候, 能够释放cpu
    所以可以使用 wait
  2. PriorityQueue本身是线程不安全的, 所以要加上锁

为什么不用PriorityBlockingQueue?
因为PriorityBlockingQueue只能处理队列为空的时候阻塞, 不能处理时间没到的阻塞
如果使用PriorityBlockingQueue, 还是需要再加一把锁, 此时代码就更加复杂了引入了两把锁, 稍有不慎, 就容易引起死锁!

public MyTimer(MyTimerTask myTimerTask, long delay){
        Object locker = new Object();//创建锁对象
        Thread thread = new Thread(() -> {
            try{
                while(true){
                    synchronized (locker){
                        //如果任务队列为空,就阻塞等待
                        if(queue.size() == 0){
                            locker.wait();
                        }
                        MyTimerTask task = queue.peek();
                        //如果此时的时间 >= 要执行的任务的时间, 则执行任务, 并出队列, 
                        // 反之则阻塞等待task.getTime() - curTime这些时间, 时间到了就执行任务
                        long curTime = System.currentTimeMillis();
                        if(task.getTime() >= curTime){
                            task.run();
                            queue.poll();
                        }else{
                            locker.wait(task.getTime() - curTime);
                        }
                    }
                }
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        });
        thread.start();
    }

那么上述等待为什么不用sleep?

  1. sleep睡着了就是真的睡着了, 不能像wait一样可以唤醒
    此时, 如果来了一个比队首元素执行时间还要进的元素, 那么我们就需要唤醒等待, 并且重新安排等待时间, 这时sleep做不到的
  2. 最重要的一点, 就是sleep等待期间不会释放锁, wait会释放锁
    如果不释放锁, 怎么进行入队操作呢?

4. 实现schedule方法

这个方法主要是为了添加任务, 并创建线程, 利用线程完成任务

public void schedule(Runnable runnable, long delay){
       synchronized (locker){
           MyTimerTask myTimerTask = new MyTimerTask(runnable, delay);
           queue.offer(myTimerTask);
           //每次添加新的任务, 都要唤醒wait, 重新判断队首元素是否要执行
           locker.notify();
       }
    }

完整代码:

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 this.time;
    }

    @Override
    public int compareTo(MyTimerTask o) {
        return (int)(this.time - o.time);
        //return (int)(o.time - this.time);
    }
}
class MyTimer{
    private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
    private Object locker = new Object();//创建锁对象


    public MyTimer(){
        Thread thread = new Thread(() -> {
            try{
                while(true){
                    synchronized (locker){
                        //如果任务队列为空,就阻塞等待
                        if(queue.size() == 0){
                            locker.wait();
                        }
                        MyTimerTask task = queue.peek();
                        //如果此时的时间 >= 要执行的任务的时间, 则执行任务, 并出队列,
                        // 反之则阻塞等待task.getTime() - curTime这些时间, 时间到了就执行任务
                        long curTime = System.currentTimeMillis();
                        if(task.getTime() <= curTime){
                            task.run();
                            queue.poll();
                        }else{
                            locker.wait(task.getTime() - curTime);
                        }
                    }
                }
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        });
        thread.start();
    }

    public void schedule(Runnable runnable, long delay){
       synchronized (locker){
           MyTimerTask myTimerTask = new MyTimerTask(runnable, delay);
           queue.offer(myTimerTask);
           locker.notify();
       }
    }
}
public class Demo30 {
    public static void main(String[] args) {
        MyTimer timer = new MyTimer();
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello 3000");
            }
        },3000);
        //可以设多个定时器
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello 2000");
            }
        },2000);
    }
}

执行结果:
在这里插入图片描述

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

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

相关文章

PMP核心知识点—之项目运行环境

知识点1&#xff1a;项目合规性 不合规的项目就不应该做。 知识点2&#xff1a;项目影响两大因素 组织过程资产、事业环境因素 知识点3&#xff1a;职能型以及项目型组织结构 知识点4&#xff1a;矩阵型组织结构 弱矩阵—平衡矩阵—强矩阵 (项目经理权力的强弱) 知识点5&am…

Oracle rac模式下undo表空间爆满的解决

文章目录 前言一、确认对应实例的undo表空间二、确认对应实例undo的文件位置三、确认回滚段使用情况四、检查undo segment状态五、创建新的undo表空间并进行切换六、等待原undo表空间segment状态变更为offline七、删除原undo表空间以及数据文件 前言 一、确认对应实例的undo表空…

如何把自动获取的ip地址固定

在大多数网络环境中&#xff0c;‌设备通常会自动从DHCP服务器获取IP地址。‌这种动态分配IP的方式虽然灵活方便&#xff0c;‌但在某些特定场景下&#xff0c;‌我们可能需要将设备的IP地址固定下来&#xff0c;‌以确保网络连接的稳定性和可访问性。‌本文将详细介绍如何把自…

C++知识点概述

C知识点概述 C是一种功能强大的编程语言&#xff0c;广泛应用于系统软件、游戏开发、高性能服务器和客户端应用等领域。在大学课程中&#xff0c;C通常作为计算机科学和软件工程学科的核心课程之一。以下是C的一些关键知识点&#xff0c;这些知识点构成了大学课程的基础。 1.…

CTF入门之奇怪的密码及图形编码总结篇(持续更新中ing)

CTF入门之奇怪的编码及图形编码(持续更新中ing UTF-8&#xff0c;unicode乱码社会主义核心价值观编码&#xff1a;在线解码&#xff1a; 与佛论禅&#xff1a;在线解密网站&#xff1a; 与熊论道&#xff1a;在线网站解密&#xff1a; 兽音&#xff1a;在线网站解密&#xff1a…

MySQL数据库(0)—— 云服务器安装MySQL

本篇主要介绍在Ubuntu环境下安装MySQL 一&#xff0c;查看系统版本 lsb_release -a二&#xff0c;添加MySQL APT 源 2.1 访问下载页面并下载发布包 传送门&#xff1a;MySQL :: Download MySQL APT Repository 下载之后&#xff0c;就可以通过 rz 命令上传到云服务器上&…

大牛荐书:美团王慧文清华大学产品经理课推荐阅读书单

美团网的王兴大家应该都很熟悉&#xff0c;作为美团的掌舵人和1号位&#xff0c;带领美团于2018年9月在港交所上市&#xff0c;公司市值在国内互联网大厂中排名第4&#xff0c;公司全职员工数11.5万人&#xff0c;2023年实现营业收入2767亿元&#xff0c;利润233亿元。 相比王兴…

MATLAB下的粒子滤波例程|三维非线性模型|组合导航|PF代码(无需下载,直接复制到MATLAB上即可运行)

文章目录 模型与程序概述PF介绍运行结果程序代码各部分模块的作用模型与程序概述 模拟INS、GPS组合导航的应用背景。在模型预处理的时候通过迭代计算三维位置的真值和滤波前(模拟纯INS计算)的值。 通过 P F PF PF(粒子滤波, p

WorkPlus安全即时通讯:端到端加密开启信息保密新时代

在数字化时代&#xff0c;信息的保密性和安全性变得越发重要。企业和个人需要确保他们的敏感信息和机密通讯不会落入黑客或第三方的手中。为了满足这一需求&#xff0c;WorkPlus安全即时通讯平台应运而生。作为一款拥有端到端加密功能的通讯平台&#xff0c;WorkPlus着重于保护…

8.30 容器部署project-exam-system项目

一、项目-使用docker部署project-exam-system 1、背景 在一台主机之内&#xff0c;实现容器的编排&#xff0c;发布考试系统 2、环境准备 # 部署docker环境 [rootdocker ~]# sh docker.sh [rootdocker ~]# vim /etc/docker/daemon.json { "registry-mirrors…

【 html+css 绚丽Loading 】 000031 三元轮回盘

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享htmlcss 绚丽Loading&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495…

揭秘Flutter职业版图:深度剖析最新Flutter岗位招聘动态

Flutter作为谷歌开发的开源移动UI框架&#xff0c;近年来在跨平台应用开发领域获得了显著的关注和应用。 随着Flutter的广泛应用&#xff0c;企业对于Flutter开发者的需求也在不断增加。这不仅仅体现在初创公司中&#xff0c;许多大型企业和科技公司也在积极寻找Flutter专业人…

智能体进化发展了一年,现在的RPA Agent迭代到什么程度了?

智能体进化发展了一年&#xff0c;现在的RPA Agent迭代到什么程度了&#xff1f;抓取豆瓣信息、自己制作PPT&#xff0c;这款AI Agent真的实现了流程全自动化AI Agent构建到执行全自动化&#xff0c;持续进化RPA Agent再次降低智能体应用门槛C端AI Agent构建与应用实现端到端&a…

1万多条华夏对联春联大全ACCESS\EXCEL数据库

对联是华#夏文化独有的一种文学艺术形式&#xff0c;讲究对仗工整&#xff0c;平仄协调&#xff0c;有固定格式和统一要求。今天这个数据库精选了古今流传的各类文字联上万余条。 分类情况统计&#xff1a;1春联&#xff08;3234&#xff09;、2婚联&#xff08;805&#xff09…

「C++系列」多态

【人工智能教程】&#xff0c;前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。 点击跳转到网站&#xff1a;【人工智能教程】 文章目录 一、多态1. 虚函数&#xff08;Virtual Functions&#xff09;2. …

多线程——概念

​ 进程 操作系统中进行资源分配的基本单位&#xff0c;进程就是进行中的程序&#xff0c;处于运行过程中的。 三个基本特征&#xff1a; 独立性&#xff1a;进程是独立运行&#xff0c;独立获取资源&#xff0c;独立接收调度的基本单。没有进程本身的统一&#xff0c;其他…

无人机专业大学生参与无人机飞手执照培训技术分析

无人机专业的大学生参与无人机飞手执照培训&#xff0c;不仅是获取职业资质的重要步骤&#xff0c;也是提升专业技能和就业竞争力的关键。以下是对无人机专业大学生参与无人机飞手执照培训的技术分析&#xff1a; 1. 培训内容的全面性 无人机飞手执照培训涵盖了从无人机基础知…

数据取证:GetData Forensic Explorer,强大的文件分析和数据恢复工具

天津鸿萌科贸发展有限公司是 GetData 公司 Forensic Explorer 电子数据取证调查软件的授权代理商。 Forensic Explorer 是一款适合新手和经验丰富的调查员使用的取证工具&#xff0c;它将灵活易用的图形界面与高级排序、过滤、关键词搜索、数据恢复和脚本技术相结合。可以快速处…

5个惊人策略:打造完美的企业内部知识库

引言&#xff1a; 在这个快速变化的商业环境中&#xff0c;拥有一个结构化和高效的内部知识库不再是可选项&#xff0c;而是企业成功的必需品。SaaS&#xff08;Software as a Service&#xff0c;软件即服务&#xff09;平台的兴起&#xff0c;为企业打造这样的内部知识库提供…

一文带你springai+ollama实现chat

Spring AI Spring AI 项目旨在简化开发集成了人工智能功能的应用程序&#xff0c;而不增加不必要的复杂性。该项目从一些著名的 Python 项目中汲取灵感&#xff0c;例如 LangChain 和 LlamaIndex&#xff0c;但 Spring AI 并不是这些项目的直接移植。该项目的创立基于这样的信念…