「JavaEE」多线程案例分析2:实现定时器

news2025/3/1 20:14:22

🎇个人主页:Ice_Sugar_7
🎇所属专栏:JavaEE
🎇欢迎点赞收藏加关注哦!

实现定时器

  • 🍉简介
  • 🍉模拟实现定时器

🍉简介

定时器类似一个闹钟,时间到了之后就会执行相应的任务
Java 标准库中已经实现了一个定时器的类 Timer

Timer timer = new Timer();

在定义好 timer 之后可以调用 schedule 把一个或多个任务(TimerTask)添加到定时器中

timer.schedule(new TimerTask() {
    @Override
    public void run() {
        System.out.println("2000 ms");
    }
},2000);

第一个参数就是任务内容,每个任务后面都会带有一个时间(第二个参数),这个时间是“相对时间”,是以 schedule 时的时间为基准,过了相对时间后才执行
比如 2000ms,它表示调用 schedule 后再过 2000ms 就会执行这个任务

TimerTask 里面有一个 run 方法,而 run 是线程的入口,说明 timer 创建了一个线程来执行任务。这个线程是前台线程,它会阻止主线程结束,需要我们使用 cancel 主动结束,否则 Timer 不知道其他地方是否会继续添加任务

public static void main(String[] args) {
    Timer timer = new Timer();
    timer.schedule(new TimerTask() {
        @Override
        public void run() {
            System.out.println("2000 ms");
            timer.cancel(); //结束线程
        }
    },2000);
    timer.schedule(new TimerTask() {
        @Override
        public void run() {
            System.out.println("1000 ms");
        }
    },1000);
}

在这里插入图片描述


🍉模拟实现定时器

首先要有一个数据结构负责保存 schedule 的任务(相当于任务清单),因为我们是先执行时间近的任务(比如有两个任务,一个是两点执行,另一个是两点半执行,肯定要先完成前者),换而言之,任务之间是有优先级的,所以要用优先级队列
标准库中提供了 PriorityQueue 和 PriorityBlockingQueue,前者是线程不安全的,后者是线程安全的,在此处的场景中 PriorityBlockingQueue 不太好控制,容易出问题,所以我们用前者

(补充:TreeSet 和 TreeMap 虽然也是有序的,但是获取到最小值的时间复杂度为 O(logN),不及 O(1) 的优先级队列)

然后需要有一个线程不断扫描优先级队列的队首元素,看它时间到了没

public class MyTimer {
    private Thread t;
    private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
    private Object locker = new Object();
    public MyTimer(){
        //定时器构造方法的主体就是启动线程,让它去扫描队首元素
        t = new Thread(() -> {
            while (true) {
                synchronized (locker) {
                    while (queue.isEmpty()) {
                        try {
                            locker.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    MyTimerTask task = queue.peek();
                    long curTime = System.currentTimeMillis();
                    if (curTime >= task.getTime()) { //时间到了,执行任务
                        task.run();
                        queue.poll(); //记得执行后把它出队列
                    } else {
                        try {
                            locker.wait(task.getTime() - curTime); //如果还没到执行时间,那就等待,不要一直循环下去
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }
        });
        t.start();
    }

    public void schedule(Runnable runnable,long delay) { //把任务添加到 queue 里面
        synchronized (locker) {
            MyTimerTask task = new MyTimerTask(runnable, delay);
            queue.offer(task);
            locker.notify(); //添加元素后,就可以唤醒处于 wait 状态的线程
        }
    }
}

队列的元素——任务,它是一个类。它的成员变量应该包括时间、能让它跑起来的 Runnable 接口

public class MyTimerTask{
    private long time; //执行任务的时间(注意这个是“绝对时间”)
    private Runnable runnable; //持有 Runnable 接口可以调用它的 run 方法,也可以不持有 Runnable,而是实现 Runnable 接口并重写 run 方法

    MyTimerTask(Runnable runnable,long time) {
        this.runnable = runnable;
        this.time = time + System.currentTimeMillis(); //什么时候执行任务:现在的时间 + 相对时间
    }

    public void run() {
        runnable.run();
    }
}

因为优先级队列要求元素是可排序的,所以我们需要实现 Comparable 接口并重写 compareTo 方法

public class MyTimerTask implements Comparable<MyTimerTask>{
    private long time; //执行任务的时间(注意这个是“绝对时间”)
    private Runnable runnable; //持有 Runnable 接口可以调用它的 run 方法,也可以不持有 Runnable,而是实现 Runnable 接口并重写 run 方法

    MyTimerTask(Runnable runnable,long time) {
        this.runnable = runnable;
        this.time = time + System.currentTimeMillis(); //什么时候执行任务:现在的时间 + 相对时间
    }

    public void run() {
        runnable.run();
    }

    @Override
    public int compareTo(MyTimerTask o) {
        return (int) (o.time-this.time); //时间小的优先级更高
    }
}

补充:compareTo 方法里面是 o.time-this.time 还是 this.time - o.time,不用去刻意记忆,两种都试一下就 ok 了

测试一下:

public class TestDemo {
    public static void main(String[] args) throws InterruptedException {
        MyTimer myTimer = new MyTimer();
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("3000 ms");
            }
        },3000);
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("1000 ms");
            }
        },1000);
    }
}

在这里插入图片描述

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

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

相关文章

【全开源】JAVA国际版多语言语聊大厅语音聊天APP系统源码

JAVA国际版多语言语聊大厅语音聊天APP系统源码——深度解析市场需求&#xff0c;打造全球化语音社交平台 随着全球化的推进和移动互联网的普及&#xff0c;人们对于语音聊天的需求日益增长。尤其是在国际交流日益频繁的今天&#xff0c;一个支持多语言、覆盖全球用户的语音聊天…

分布式搜索-elaticsearch基础 概念

什么是elaticsearch: 倒排索引&#xff1a;就是将要查询的内容分成一个个词条&#xff0c;在将词条文档id存入&#xff0c;词条是唯一的。 文档词条总结: mysql和Elasticsearch概念对比: 架构: 基本概念总结:

互联网盲盒小程序开发,提高商家在市场中的竞争力

随着人们生活水平的提高&#xff0c;对娱乐消费需要也在慢慢增加&#xff0c;潮玩市场也因此得到了快速发展&#xff0c;尤其是盲盒&#xff0c;深受大众的喜爱&#xff0c;现在在各大商场以及各种社交平台上都能看到盲盒的身影&#xff0c;市场影响力非常大&#xff01; 在当…

FastAPI:Python打造高效API的终极武器

在Python的世界里&#xff0c;如果你想要一个既快速又现代的方式来构建API&#xff0c;那么FastAPI可能是你的首选。这个库基于Starlette&#xff08;用于Web编程&#xff09;和Pydantic&#xff08;用于数据验证&#xff09;&#xff0c;专门为速度和易用性设计。 什么是FastA…

搞懂Docker(九)- 使用Docker Compose

获取示例程序 示例程序 或者 示例程序 获取示例程序程序结构如下├── getting-started-app/ │ ├── package.json │ ├── README.md │ ├── spec/ │ ├── src/ │ └── yarn.lock使用Docker Compose Docker Compose是一个帮助你定义和共享多容器应用程序的工具…

Linux系统编程:进程控制

1.进程创建 1.1 fork函数 fork&#xff08;&#xff09;通过复制调用进程来创建一个新进程。新进程称为子进程&#xff0c;是调用进程的精确副本 进程&#xff0c;但以下几点除外&#xff1a; 子进程有自己的PID&#xff0c;此PID与任何现有进程组的ID不匹配子进程的父进程ID…

qt: undefined reference to `vtable for aaa‘

版本qt4.8.6&#xff0c;编译报错“main.cpp:(.text0x3b): undefined reference to vtable for aaa” 就一个main.cpp #include <QApplication> #include <QTimer> #include <QCursor> #include <QMouseEvent> #include <QDesktopWidget> #inc…

代驾+顺风车+货运app功能介绍

代驾货运顺风车同城拼车打车网约车系统源码app小程序是一个功能丰富的平台&#xff0c;支持二次开发和定制&#xff0c;以满足不同运营商的需求。以下是关于该系统的功能详情介绍&#xff1a; 一、核心功能 会员管理&#xff1a;包括用户注册、登录、个人信息管理等功能&…

yarn 安装以及报错处理

前一种报错是由于没有安装yarn导致的&#xff0c;使用以下命令即可安装&#xff1a; npm install -g yarn 如果成功安装&#xff0c;将显示Yarn的版本号。 yarn --version 第二种报错是因为系统上的执行策略限制导致的。执行策略是一种安全功能&#xff0c;用于控制在计算机…

第15节 编写shellcode加载器

我最近在做一个关于shellcode入门和开发的专题课&#x1f469;&#x1f3fb;‍&#x1f4bb;&#xff0c;主要面向对网络安全技术感兴趣的小伙伴。这是视频版内容对应的文字版材料&#xff0c;内容里面的每一个环境我都亲自测试实操过的记录&#xff0c;有需要的小伙伴可以参考…

HCIP的学习(17)

BGP基础配置 使用直连接口IP地址来建立EBGP对等体关系 1、启动BGP协议 [r1]bgp 100 ----启动BGP协议&#xff0c;并且规定其AS号2、配置设备的RID数值&#xff0c;一般选择设备的loopback接口的IP地址 [r1-bgp]router-id 1.1.1.13、配置BGP对等体信息&#xff0c;包含了对等体…

【声呐仿真】学习记录3-待续

【声呐仿真】学习记录3-后续 第五阶段-获取数据1.运行赫尔库勒斯沉船的世界&#xff1a;2.键盘操纵rov至合适的位置&#xff0c;调整Image topic&#xff0c;查看输出图像3.RVIZ SONAR 图像查看器插件&#xff08;没有对应的topic&#xff09;4.点云5.录制rosbag 第六阶段-查看…

问题—前端调用接口url多加一个/,本地可以调通,测试环境报错302,分开调两个接口

问题背景 接口url前面多加一个/ &#xff0c;npm run serve 起项目&#xff0c;本地调用正常 npm run build 打包到测试环境&#xff0c;接口出现问题&#xff0c;分开调用接口&#xff0c;且报302错误 问题原因&#xff1a; 本地开发环境和测试环境的URL处理方式不同 本地使…

专业的服贸会服务团队-媒体邀约宣传

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 2024服贸会开展在即&#xff0c;许多企业都做好了的参展的准备&#xff0c;北京麦塔文化提供专业的展览展会服务&#xff0c;下面做个简单介绍。 、会场搭建团队&#xff1a; 负责整个活…

Ansys Zemax|HUD 设计实例

说明 本文介绍了HUD设计实例。 实例说明 规格如下&#xff1a; 显示器尺寸&#xff1a;24*8mm 眼盒尺寸&#xff1a;100*40mm 放大倍率&#xff1a;5 &#xff08;虚像尺寸 120*40mm&#xff09; 虚像距离&#xff1a;1.8m 最终光学系统的整体布局如下图所示。 从HUD发出的…

478.8-480W 宽电压输入 AC/DC 导轨式开关电源——TPR/SDR-480-XS 系列

TPR/SDR-480-XS 系列导轨式开关电源&#xff0c;额定输出功率为478.8-480W&#xff0c;产品输入范围&#xff1a;85-264VAC。提供24V、36V、48V输出&#xff0c;具有短路保护&#xff0c;过载保护等功能&#xff0c;并具备高效率&#xff0c;高可靠性、高寿命、更安全、更稳定等…

GRFB-UNet:一种新的多尺度注意力网络,用于铺路分割

不同场景下的带注释的触觉铺装示例: GRFB-UNet网络结构: GRFB模块的结构: 铺路在视障人士的旅行中起着至关重要的作用。因此,识别铺装的形状和位置以支持视障人士的移动性是相当有意义的,而视觉分割技术就适合这项任务。为了有效提高触觉铺装分割的精度和鲁棒性,…

RS485和RS232区别

RS485和RS232接口在物理外观上的区别主要在于连接器的类型和接线方式上。这两种串行通讯接口虽然在功能上有所不同&#xff0c;但外观上也有一些显著的特点。 连接器类型&#xff1a; RS232 接口通常使用DB9或DB25类型的连接器。DB9是较为常见的&#xff0c;拥有9个针脚&#x…

小程序的小组件

进度的组件 文字换行过滤 以及 排序 简单易懂 只为了记录工作 <template><div><ProgressBar :progress"progress" /><button click"increaseProgress">增加进度</button><view class"goods-name">12…

Ubuntu18.04安装OpenCV和OpenCV_Contrib

1. &#xff08;如果需要&#xff09;卸载已安装的OpenCV库 删除opencv其他的相关文件 sudo rm -r /usr/local/include/opencv2 sudo rm -r /usr/local/include/opencv sudo rm -r /usr/include/opencv sudo rm -r /usr/include/opencv2 sudo rm -r /usr/local/share/opencv…