JavaEE 第11节 定时器

news2024/12/28 20:30:18

前言

本篇博客重点介绍定时器的简单实现,帮助理解其底层原理。关于JAVA工具类自带的定时器,只会简单介绍,详细使用参阅官方文档(下文中有官方文档的连接)。

一、什么是定时器

定时器的概念非常简单。

它在软件开发中应用非常广泛。它类似于一个提前设定好的闹钟,到达指定时间后,执行设定好的任务程序。比如在网络通信中,如果在一个时间范围内没有得到响应,那么重新尝试连接网络等。

二、Java标准库中的定时器

标准库中提供了Timer类,核心方法是schedule(用于安排任务给计时器),它有两个参数:

  • TimerTask task:描述要执行的任务,实际是对Runnable的封装
  • long delay:延迟多长时间执行 ms     

如图(来自官方文档):

代码演示:

一个Timer实例只会创建一个线程(默认创建的前台线程),因此如果有多个schedule的任务,他们是串行执行的。关于Timer类的详细信息和使用请参阅oracle官方文档:Timer

如果有在多线程环境下使用计时器的需要,需要用到线程池中的ScheduledExecutorService.具体使用请参阅oracle官方文档:ScheduledExecutorService

三、设计实现一个简单的定时器

这里介绍的计时器,只包含一个线程,并且可能会出现任务阻塞(如果一个任务执行时间过长,后执行的任务可能延迟执行)

/**
 * 我们会使用优先级队列来判断那个任务最紧急需要先完成,所以需要实现Compareble,
 * 然后重写compareTo方法,用时间戳进行大小比较
 * 优先级队列中,存放的是一个一个要执行的任务
 */
class MyTimerTask implements Comparable<MyTimerTask> {

    //用绝对时间——时间戳来表示时间间隔
    long time;

    //要执行的任务
    Runnable runnable;

    //构造方法
    public MyTimerTask(Runnable runnable, long delay) {

        //执行时间等于当前时间+延迟时间大小
        this.time = System.currentTimeMillis() + delay;

        this.runnable = runnable;
    }

    //用于执行任务
    public void run() {
        runnable.run();
    }

    //获取需要执行的任务的执行时间,等一下有用
    public long getTime() {
        return time;
    }

    @Override
    public int compareTo(MyTimerTask o) {
        //创建一个小根堆,时间到的先执行
        return (int) (this.time - o.time);
    }
}


class MyTimer {

    //把schedule的任务放到这里,然后去除优先级大的任务,判断时间,是否需要执行
    PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
    //创建一个锁对象
    private Object locker = new Object();

    public void schedule(Runnable runnable, long delay) {

        //涉及优先级队列的读写操作,需要加锁保证线程安全
        synchronized (locker) {
            //把runnable和delay封装到myTimerTask中
            MyTimerTask myTimerTask = new MyTimerTask(runnable, delay);

            //放入优先级队列
            queue.offer(myTimerTask);

            //通知唤醒myTimer创建的线程
            locker.notify();
        }
    }

    //构造方法,一旦创建这个实例,开始在优先级队列里面循环获取方法执行(如果到达要执行的时机)
    public MyTimer() {
        Thread t = new Thread(() -> {

            while (true) {
                try {
                    //下面涉及对优先级队列的读写操作,需要保证线程安全
                    synchronized (locker) {

                        //目前没有任务,就一直睡,直到schedule了新的任务
                        while (queue.isEmpty()) {
                            locker.wait();
                        }
                        
                        //有了任务,先看看当前需不需要执行
                        MyTimerTask task=queue.peek();
                        
                        //时间已经到了,或者超时了,就执行
                        if(System.currentTimeMillis()>=task.time){
                            task.run();
                            //一处执行完的任务
                            queue.poll();
                        }else{
                            //没到时间,就睡到任务执行的时候
                            locker.wait(task.time-System.currentTimeMillis());
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        //启动线程
        t.start();
    }
}

public class Test {
    public static void main(String[] args) throws InterruptedException {
        MyTimer myTimer = new MyTimer();
        myTimer.schedule(() -> {
            System.out.println("hello 1000");
        }, 1000);

        myTimer.schedule(() -> {
            System.out.println("hello 2000");
        }, 2000);

        myTimer.schedule(() -> {
            System.out.println("hello 3000");
        }, 3000);

        System.out.println("程序开始运行");
    }
}

执行结果:

此程序需要注意的几个点:

1)不要把下面的while换成if语句:

2)不能使用Thread.slee()替换wait()

原因有2

1、Thread.sleep在睡眠的时候是不会释放锁的(抱着锁睡),也就是睡眠的时候,schedule操作是无法进行的,会造成死锁

2、notify和wait本来就是搭配起来用,实现线程之间通信的。

如果在myTimer线程睡眠的时候,有新的任务进来,notify会重新唤醒线程,而Thread.sleep是没有这个功能的。

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

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

相关文章

Umi-OCR 图片文字智能识别软件,不需联网,Linux 下运行错误修复,AI 识文一文搞定!

Umi-OCR 图片文字智能识别软件&#xff0c;不需联网&#xff0c;Linux 下运行除错修复&#xff0c;AI 识文一文搞定&#xff01; Umi-OCR 是一款开源的图片文字只能识别软件&#xff0c;支持多国语言识别&#xff0c;支持截图OCR / 批量OCR / PDF识别 / 二维码 / 公式识别。 …

C:数组传参的本质

1、一维数组传参的本质 数组传参是指在函数调用时将数组作为参数传递给函数。 int main() {int arr[10] { 1,2,3,4,5,6,7,8,9,10 };test(arr);return 0;}数组传参只需要写数组名就可以了。注意&#xff1a;数组名是arr&#xff0c;而不是arr[10] 数组传参形参该怎么写呢&am…

Linux git安装与部署

目录 git安装 1、下载与安装 2、配置git账号信息 创建本地仓库 1、创建本地代码库文件夹 2、创建项目代码本地仓库文件夹 3、进入到projCode目录下&#xff0c;创建git本地仓库 4、创建过滤文件.gitignore 5、添加.gitignore到git暂存区 6、提交.gitignore 7、将项目…

Spring Boot集成钉钉群通知机器人

文章目录 一、钉钉机器人配置1&#xff09;添加机器人2&#xff09;添加自定义机器人3&#xff09;设置机器人参数4&#xff09;添加机器人完成 二、依赖导入三、工具类封装四、关键字推送消息测试类1&#xff09;测试类2&#xff09;程序输出结果&#xff1a;3&#xff09;通知…

基于模糊神经网络的金融序列预测算法matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 基于模糊神经网络的金融序列预测算法matlab仿真,根据序列的MAD,RSI,KD等指标实现序列的预测和最终收益分析。 2.测试软件版本以及运行结果展示 MATLAB2022A版本…

使用Go语言将PDF文件转换为Base64编码

使用 Go 语言将 Base64 编码转换为 PDF 文件-CSDN博客本文介绍了如何使用 Go 语言将 Base64 编码转换为 PDF 文件&#xff0c;并保存到指定路径。https://blog.csdn.net/qq_45519030/article/details/141225772 在现代编程中&#xff0c;数据转换和编码是常见的需求。本文将介绍…

Ubuntu安装mysql 以及远程连接mysql Windows—适合初学者的讲解(详细)

目录 准备工作 一.Xshell中操作 &#xff08;1&#xff09;在虚拟机中安装mysql &#xff08;2&#xff09;连接Windows数据库 &#xff08;3&#xff09;进入linux数据库。 &#xff08;4&#xff09;修改mysql配置文件 二.Windows命令窗口操作 需要软件虚拟机&#xff0c;Xsh…

idea付费插件,哪个比较好用?

以下idea付费插件你们都用过哪些呢&#xff1f; 获取链接&#xff1a;https://web.52shizhan.cn

JUC3-共享模型之管程

共享带来的问题 分析 两个线程对初始值为 0 的静态变量一个做自增&#xff0c;一个做自减&#xff0c;各做 5000 次&#xff0c;结果是 0 吗&#xff1f; 分析&#xff1a; 以上的结果可能是正数、负数、零 因为Java中对静态变量的自增、自减并不是原子操作&#xff0c;需要…

三级_网络技术_29_网络安全技术

1.下列关于RAID的描述中&#xff0c;错误的是()。 服务器需要外加一个RAID卡才能实现RAID功能 RAID10是RAID0和RAID1的组合 些RAID卡可以提供SATA接口 RAID卡可以提供多个磁盘接口通道 2.下列关于RAID的描述中&#xff0c;错误的是()。 RAID5可靠性优于RAID1 些RAID卡可…

【Java 并发编程】(三) 从CPU缓存开始聊 volatile 底层原理

并发编程 三大问题 在并发编程中&#xff0c;原子性、有序性和可见性是三个重要的问题&#xff0c;解决这三个问题是保证多线程程序正确性的基础。原子性: 指的是一个操作不可分割, 要么全部执行完成, 要么不执行, 不存在执行一部分的情况.有序性: 有序性是指程序的执行顺序与…

Arco Chatter - 改变 Chatter Panel 交互体验的插件

关于 Chatter Panel Arco Chatter 插件是一款专门为 odoo 系统 Chatter Panel 设计的实用工具&#xff0c;它具有改变 Chatter Panel 展开/收起状态和调整 Chatter Panel 宽度的功能。 Arco Chatter 插件主要用于优化用户在 odoo 系统中的操作体验。通过插件功能&#xff0c;用…

【iOS】—— JSONModel

JSONModel源码 1. JSONModel介绍2. JSONModel的其他用法2.1 转换属性名称2.2 自定义错误 3. 源码分析3.1 - (id)initWithDictionary:(NSDictionary*)dict error:(NSError**)err3.2 JSONModel持有的数据3.3 load3.4 JSONModel的init方法3.5 __inspectProperties方法3.6 JSONMode…

动态创建 Delphi 按钮的完整指南:基于配置文件的 `TGridPanel` 实现

在 Delphi 开发中&#xff0c;我们经常需要根据不同的配置动态生成 UI 元素。本文将带你通过一个完整的示例&#xff0c;演示如何根据配置文件动态创建按钮&#xff0c;并将它们排列在一个 TGridPanel 中。每个按钮的标题、链接、颜色和大小都将从配置文件中读取。 “C:\myApp\…

基于YOLOv8-pose的手部关键点检测(1)- 手部关键点数据集获取(数据集下载、数据清洗、处理与增强)

前言 手部姿态估计、手势识别和手部动作识别等任务时&#xff0c;可以转化为对手部关键点的分布状态和运动状态的估计问题。本文主要给出手部关键点数据集获取的方式。 总共获取三个数据集&#xff1a; handpose_v2&#xff1a;训练集35W张&#xff0c;验证集2.85W张&#xff1…

vim - vim模式及部分操作

文章目录 一、vim 基本介绍二、vim 的简单使用三、几种常用模式切换四、命令模式和底行模式的操作汇总 一、vim 基本介绍 vim 是一款多模式的编辑器。vim 中有很多子命令来进行代码的编写操作。 同时&#xff0c;vim 提供了不同的模式供我们选择。 在vim下的底行模式下通过:he…

如何查询婚姻状况信息?

1.使用在线查询工具&#xff1a;‌ 现在&#xff0c;‌也有一些在线查询工具&#xff0c;‌如“天远查”“全能查”等微信小程序&#xff0c;‌提供了婚姻状态查询服务。‌这些工具通常需要你提供一些基本信息&#xff0c;‌并可能收取一定的费用。‌在使用这些工具时&#xff…

WebRTC为何成为视频开发领域的首选技术? EasyCVR视频转码助力无缝视频通信

随着互联网的飞速发展&#xff0c;视频通信已成为日常生活和工作中不可或缺的一部分。从在线教育、视频会议到远程医疗、在线直播&#xff0c;视频开发的需求日益增长。在这些应用场景中&#xff0c;选择何种技术来构建视频系统至关重要。 目前&#xff0c;在很多视频业务的开…

文本纠错实现定位与标记

一、基于讯飞文本纠错实现前端标记定位&#xff0c;点击可以联动&#xff01;

VM下kali设置桥接网络

一、查看主机ip 1.winr输入cmd 2.进入终端输入ipconfig 3.查看ip 二、虚拟机网络设置 1.进入vm的虚拟网络编辑器 2.桥接网卡自己选&#xff0c;1是有线网卡2是无线网卡&#xff0c;选择记得点应用 3.虚拟机的网络适配器也要选择桥接模式 三、kali网络配置 1.打开kali终端编辑文…