什么是定时器?

news2024/11/19 1:40:09

  前言👀~

上一章我们介绍了阻塞队列以及生产者消息模式,今天我们来讲讲定时器

定时器

标准库中的定时器

schedule()方法

扫描线程

手动实现定时器

任务类

存储任务的数据结构

定时器类


如果各位对文章的内容感兴趣的话,请点点小赞,关注一手不迷路,讲解的内容我会搭配我的理解用我自己的话去解释如果有什么问题的话,欢迎各位评论纠正 🤞🤞🤞

12b46cd836b7495695ce3560ea45749c.jpeg

个人主页:N_0050-CSDN博客

相关专栏:java SE_N_0050的博客-CSDN博客  java数据结构_N_0050的博客-CSDN博客  java EE_N_0050的博客-CSDN博客


定时器

定时器是个非常常见的组件,尤其是在网络进行通信的时候,类似发邮件,类似于一个 "闹钟",达到一个设定的时间之后, 就执行某个指定好的代码

举个例子,当客户端给服务器发送请求后,服务器半天没有响应,就像你发邮件一样,发的时候会转圈圈,成功了就会显示发送成功或者什么提示信息,如果服务器没有响应,你这边可能就一直在那转圈圈。我们也不知道是什么原因造成的,可能是请求没发过去,可能是响应丢了,也可能是服务器出现了问题。所以对于客户端来说,也可以说对用户来说,肯定不能一直等啊那体验多不好啊,所以设置一个等待时间(最大的期限),过了这个等待时间把电脑砸了,开个玩笑,过了这个最大期限,我们选择重新发一遍,或者直接不发,或者重开这个程序等等方式。这里的最大期限我们可以使用定时器去实现


标准库中的定时器

首先我们先使用一下定时器Timer类,再去讲解,代码如下

public class Test1 {
    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("启动成功");
            }
        }, 1000);
        System.out.println("原神启动");
    }
}

输出结果


schedule()方法

这个方法涉及两个参数 第一个参数描述了任务要做什么这里使用匿名内部类去创建一个TimerTask实例第二个参数就是时间就是要在多长时间(单位为毫秒)后去执行,这个时间是根据当前时间为准然后根据你设定的时间来执行任务的,比如说现在11:00:00你设置1秒后执行就是11:00:01执行任务。然后前面用匿名内部类创建出来的TimerTask实例实现了Runnable接口,然后我们重写方法定义自己要执行的任务通过schedule方法,接着再由扫描线程去执行


扫描线程

当我们创建出这个timer对象后,这个线程也就被创建出来了,后续要执行任务,都是通过这个线程去执行的

来看刚才这段代码以及输出结果

public class Test1 {
    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("启动成功");
            }
        }, 1000);
        System.out.println("原神启动");
    }
}

输出结果,你会发现整个进程并没有结束,主线程执行schedule方法的时候,是把这个任务丢给timer对象中的一个线程去处理的,这个线程可以叫"扫描线程",你设置的时间一到,就去扫描任务也就是执行你写的任务。

解释:为什么整个进程没有结束?timer中的这个线程阻止了进程结束,它在等我们再给它安排任务,相当于服务员,你有什么吩咐它就执行,没有任务就在那等并且timer里可以安排多个任务


手动实现定时器

根据上面标准库可以得出以下要求:

1.和上面标准库提供的timer类一样,我们需要一个扫描线程,然后去执行任务

2.需要一个数据结构,把所有要执行的任务保存起来

3.需要使用一个类,通过一个类的对象去来描述执行的任务(任务内容和执行时间)


任务类

首先写一个用来描述任务的类,包含任务内容和执行时间

在设置任务执行时间的时候,有两种方式,一种是相对的时间,一种是绝对的时间(完整的时间戳),两种都可以这里我们选择绝对时间,因为相对时间要计算间隔后的时间然后进行比较,绝对时间获取当前时间戳加上任务执行时间然后进行比较。

下面是任务类的实现,不只这一种,最后完整代码有两种

//用来描述任务的类 包含任务的内容和执行时间
class MyTimerTask {
    private Runnable runnable;
    private long time;

    public MyTimerTask(Runnable runnable, long delay) {
        this.runnable = runnable;
        this.time = System.currentTimeMillis() + delay;//使用绝对时机 时间戳+传入的时间
    }

    public Runnable getRunnable() {
        return runnable;
    }

    public long getTime() {
        return time;
    }
}


存储任务的数据结构

这里的数据结构我们采用优先级队列去保存需要执行的任务,因为我们肯定要先执行时间最少的任务,然后优先级队列也就是堆,最顶层的就是最小的,并且优先级队列取出元素(也就是获取时间最少的任务)时间复杂度都为O(1)

    public PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();

但是注意优先队列要求放入的元素是可以比较的,也就是我们的任务之间可以进行比较,所以我们还需要实现自定义比较器,使用时间进行比较除了优先级队列中的元素需要能进行比较的,还有二叉搜索树也就是TreeMap和TreeSet

    public PriorityQueue<MyTimerTask> queue = new PriorityQueue<>(new Comparator<MyTimerTask>() {
        @Override
        public int compare(MyTimerTask o1, MyTimerTask o2) {
            return (int) (o1.getTime() - o2.getTime());
        }
    });

定时器类

我们的定时器和标准库中的定时器一样,我们需要一个扫描线程执行任务,还需要一个schedule方法,上面的优先级队列也放在定时器中,下面是代码实现,需要注意线程不安全问题,会出现这样的可能就比如主线程在向队列添加元素的时候,扫描线程也在对队列进行判断,导致加入了元素的时候这里正好进行判断,然后为空进入阻塞状态

class MyTimer {
    //优先级队列存储任务 优先级队列的元素要能进行比较 所以要实现比较器 我们根据时间进行比较
    public PriorityQueue<MyTimerTask> queue = new PriorityQueue<>(new Comparator<MyTimerTask>() {
        @Override
        public int compare(MyTimerTask o1, MyTimerTask o2) {
            return (int) (o1.getTime() - o2.getTime());
        }
    });

    public MyTimer() {
        //创建出定时器对象的时候 启动扫描线程
        thread.start();
    }

    //给用户调用的方法 传入要完成的任务以及时间
    public void schedule(Runnable runnable, long delay) {
        synchronized (lock) {//避免线程不安全问题 有任务了就唤醒线程进行工作
            if (delay < 0) {
                throw new IllegalArgumentException("输入的时间有误");
            } else {
                queue.offer(new MyTimerTask(runnable, delay));//调用这个方法的时候 创建任务然后放进队列进行处理
                lock.notify();
            }
        }
    }

    public Object lock = new Object();
    public Thread thread = new Thread(() -> {
        synchronized (lock) {
            while (true) {//即使没任务 也等我们给它分配任务
                while (queue.isEmpty()) {//队列为空进入阻塞 使用while保险起见
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                long currentTime = System.currentTimeMillis();//记录当前时间
                MyTimerTask task = queue.peek();//先看任务的时间 如果到了再poll
                if (currentTime >= task.getTime()) {
                    queue.poll();
                    task.getRunnable().run();//获取到引用去执行用户的任务
                } else {

                }
            }
        }
    });
}

还有一个地方需要进行优化比如就是你设置执行任务的时间在10点半,然后else那块不写代码,它会一直到while循环开始判断一路下路,一直到时间到去执行任务,这样做消耗太多cpu资源,解决办法,让线程在这里休息,使用带参数的wait方法,当前执行任务时间减去当前时间作为参数

    public Thread thread = new Thread(() -> {
        synchronized (lock) {
            while (true) {//即使没任务 也等我们给它分配任务
                while (queue.isEmpty()) {//队列为空进入阻塞 使用while保险起见
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                long currentTime = System.currentTimeMillis();//记录当前时间
                MyTimerTask task = queue.peek();//先看任务的时间 如果到了再poll
                if (currentTime >= task.getTime()) {
                    queue.poll();
                    task.getRunnable().run();//获取到引用去执行用户的任务
                } else {

                }
            }
        }
    });

两种完整代码

第一种任务类是没有直接实现Runnable接口

//用来描述任务的类 包含任务的内容和执行时间
class MyTimerTask {
    private Runnable runnable;
    private long time;

    public MyTimerTask(Runnable runnable, long delay) {
        this.runnable = runnable;
        this.time = System.currentTimeMillis() + delay;//使用绝对时机 时间戳+传入的时间
    }

    public Runnable getRunnable() {
        return runnable;
    }

    public long getTime() {
        return time;
    }
}

//定时器
class MyTimer {
    //优先级队列存储任务 优先级队列的元素要能进行比较 所以要实现比较器 我们根据时间进行比较
    public PriorityQueue<MyTimerTask> queue = new PriorityQueue<>(new Comparator<MyTimerTask>() {
        @Override
        public int compare(MyTimerTask o1, MyTimerTask o2) {
            return (int) (o1.getTime() - o2.getTime());
        }
    });

    public MyTimer() {
        //创建出定时器对象的时候 启动扫描线程
        thread.start();
    }

    //给用户调用的方法 传入要完成的任务以及时间
    public void schedule(Runnable runnable, long delay) {
        synchronized (lock) {//避免线程不安全问题 有任务了就唤醒线程进行工作
            if (delay < 0) {
                throw new IllegalArgumentException("输入的时间有误");
            } else {
                queue.offer(new MyTimerTask(runnable, delay));//调用这个方法的时候 创建任务然后放进队列进行处理
                lock.notify();
            }
        }
    }

    public Object lock = new Object();
    public Thread thread = new Thread(() -> {
        synchronized (lock) {
            while (true) {//即使没任务 也等我们给它分配任务
                while (queue.isEmpty()) {//队列为空进入阻塞 使用while保险起见
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                long currentTime = System.currentTimeMillis();//记录当前时间
                MyTimerTask task = queue.peek();//先看任务的时间 如果到了再poll
                if (currentTime >= task.getTime()) {
                    queue.poll();
                    task.getRunnable().run();//获取到引用去执行用户的任务
                } else {

                }
            }
        }
    });
}

第二种是任务类实现Runnable接口

//用来描述任务的类 就是存储任务的内容以及执行时间
class MyTimerTask implements Runnable {
    private long time;

    private Runnable task;

    public MyTimerTask(Runnable runnable, long delay) {
        this.task = runnable;
        this.time = System.currentTimeMillis() + delay;//使用绝对时间 当前时间戳+多少秒后执行=执行时间
    }

    public long getTime() {
        return time;
    }

    @Override
    public void run() {//外层的这个就是一个壳,通过调用这个方法执行里面我们自己写的任务
        task.run();// 这个就是我们自己写的任务
    }
}

//定时器 包含存储队列 扫描线程 创建任务
class MyTimer {

    public Object lock = new Object();
    //使用优先级队列存储任务 因为取出任务的时间复杂度为0(1) 注意要比较器 因为我们要使用时间比较出谁是最小的
    public PriorityQueue<MyTimerTask> queue = new PriorityQueue<>(new Comparator<MyTimerTask>() {
        @Override
        public int compare(MyTimerTask o1, MyTimerTask o2) {
            return (int) (o1.getTime() - o2.getTime());
            //return Long.compare(o1.getTime(), o2.getTime());//可以避免溢出
        }
    });

    //初始化定时器就启动扫描线程
    public MyTimer() {
        thread.start();
    }

    //把任务和执行时间传到这方法 然后通过这个方法创建任务类去装任务和时间
    public void schedule(Runnable runnable, long delay) {
        synchronized (lock) {
            if (delay < 0) {
                throw new IllegalArgumentException("输入的时间有误!!!");
            } else {
                queue.offer(new MyTimerTask(runnable, delay));
                lock.notify();
            }
        }
    }

    //创建扫描线程执行任务
    public Thread thread = new Thread(() -> {
        //因为扫描线程会一直扫描任务 它在等我们再给它安排任务
        while (true) {
            synchronized (lock) {
                while (queue.isEmpty()) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //不是直接poll 任务执行的时候要和当前时间进行比较后 再进行poll去执行
                //这个拿的任务相当于我们自己写的任务
                MyTimerTask task = queue.peek();
                long currentTime = System.currentTimeMillis();
                //如果当前时间等于或者超过任务的执行时间就执行任务
                if (currentTime >= task.getTime()) {
                    task.run();//
                    queue.poll();
                } else {
                    try {
                        //让线程休息到执行任务的时间
                        lock.wait(task.getTime() - currentTime);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    });
}

以上便是本章内容,定时器在日常开发中还是会用到的,例如发邮件这类的,所以还是需要好好掌握,我们下一章再见💕

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

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

相关文章

【postgresql】数据库操作

创建数据库 使用 CREATE DATABASE SQL 语句来创建 语法&#xff1a; CREATE DATABASE dbname; 使用 createdb 命令来创建 语法&#xff1a; createdb [option...] [dbname [description]] 参数说明&#xff1a; dbname&#xff1a;要创建的数据库名。 description&…

手机电脑能实现无缝共享的记事软件是什么

在这个信息爆炸的时代&#xff0c;记事软件已成为我们日常生活中不可或缺的工具。然而&#xff0c;我曾深深困扰于手机与电脑之间记事内容无法无缝共享的问题。 记得有一次&#xff0c;我在手机上匆匆记下了一个重要的想法&#xff0c;打算稍后在电脑上展开。但当我坐在电脑前…

实战whisper第三天:fast whisper 语音识别服务器部署,可远程访问,可商业化部署(全部代码和详细部署步骤)

Fast Whisper 是对 OpenAI 的 Whisper 模型的一个优化版本,它旨在提高音频转录和语音识别任务的速度和效率。Whisper 是一种强大的多语言和多任务语音模型,可以用于语音识别、语音翻译和语音分类等任务。 Fast Whisper 的原理 Fast Whisper 是在原始 Whisper 模型的基础上进…

C语言的数据结构:图的操作

&#x1f6fa;图的遍历&#xff1a; 注意&#xff1a;在遍历的过程中&#xff0c;可能会出现 回路 ( 已经访问过的节点还要重新访问一次 ) \color{orange}回路(已经访问过的节点还要重新访问一次) 回路(已经访问过的节点还要重新访问一次). 当从A开始访问时&#xff0c;先访问…

「实战应用」如何用DHTMLX将上下文菜单集成到JavaScript甘特图中(二)

DHTMLX Gantt是用于跨浏览器和跨平台应用程序的功能齐全的Gantt图表。可满足项目管理应用程序的所有需求&#xff0c;是最完善的甘特图图表库。 DHTMLX Gantt是一个高度可定制的工具&#xff0c;可以与项目管理应用程序所需的其他功能相补充。在本文中您将学习如何使用自定义上…

秒懂设计模式--学习笔记(4)【创建篇-工厂方法模式】

目录 3、工厂方法模式3.1 介绍3.2 工厂的多元化与专业化3.3 游戏角色建模(建模)3.4 简单工厂不简单(实例化、初始化)3.5 制定工业制造标准3.6劳动分工 3、工厂方法模式 3.1 介绍 程序设计中的工厂类往往是对对象构造、实例化、初始化过程的封装&#xff0c;而工厂方法&#x…

【AIGC评测体系】大模型评测指标集

大模型评测指标集 &#xff08;☆&#xff09;SuperCLUE&#xff08;1&#xff09;SuperCLUE-V&#xff08;中文原生多模态理解测评基准&#xff09;&#xff08;2&#xff09;SuperCLUE-Auto&#xff08;汽车大模型测评基准&#xff09;&#xff08;3&#xff09;AIGVBench-T2…

昇思25天学习打卡营第6天|关于函数与神经网络梯度相关技术探讨

目录 Python 库及 MindSpore 相关模块和类的导入 函数与计算图 微分函数与梯度计算 Stop Gradient Auxiliary data 神经网络梯度计算 Python 库及 MindSpore 相关模块和类的导入 Python 中的 numpy 库被成功导入&#xff0c;并简称为 np。numpy 在科学计算领域应用广泛&#x…

借教室(题解)

P1083 [NOIP2012 提高组] 借教室 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 思路&#xff1a;二分前缀和 我们将和质检员那题差不多&#xff0c;只需要将候选人二分即可 #include<bits/stdc.h> using namespace std; #define int long long int n,m; int r[100000…

精准检测,守护安全:可燃气体报警器检测范围探讨

随着工业化进程的加快&#xff0c;易燃易爆气体的使用日益普遍&#xff0c;其安全隐患也愈发凸显。可燃气体报警器作为一种重要的安全监测设备&#xff0c;能够在气体泄漏时及时发出警报&#xff0c;预防火灾和爆炸事故的发生。 在这篇文章中&#xff0c;佰德将对可燃气体报警…

Docker搭建MySQL双主复制详细教程

在此之前需要提前安装好Docker和 Docker Compose 。 一、创建目录 首先创建一个本地数据挂载目录。 mkdir -p master1-data master2-data二、编写docker-compose.yml version: 3.7services:mysql-master1:image: mysql:5.7.36container_name: mysql-master1environment:MYSQL_…

自动驾驶---Motion Planning之多段五次多项式

1 前言 在之前的博客系列文章中和读者朋友们聊过Apollo的 Motion Planning方案: 《自动驾驶---Motion Planning之LaneChange》 《自动驾驶---Motion Planning之Path Boundary》 《自动驾驶---Motion Planning之Speed Boundary》 《自动驾驶---Motion Planning之轨迹Path优化》…

Python中解决os.listdir命令读取文件乱序问题方法

Python中使用对话框批量打开文件时出现乱序问题的解决方法 一、问题描述二、os.listdir读取文件乱序问题解决方法 欢迎学习交流&#xff01; 邮箱&#xff1a; z…1…6.com 网站&#xff1a; https://zephyrhours.github.io/ 一、问题描述 有时候为了方便&#xff0c;我们在进…

Hadoop-08-HDFS集群 基础知识 命令行上机实操 hadoop fs 分布式文件系统 读写原理 读流程与写流程 基本语法上传下载拷贝移动文件

章节内容 上一节完成&#xff1a; HDFS的简介内容HDFS基础原理HDFS读文件流程HDFS写文件流程 背景介绍 这里是三台公网云服务器&#xff0c;每台 2C4G&#xff0c;搭建一个Hadoop的学习环境&#xff0c;供我学习。 之前已经在 VM 虚拟机上搭建过一次&#xff0c;但是没留下…

从零开始学量化~Ptrade使用教程——安装与登录

PTrade交易系统是一款高净值和机构投资者专业投资软件&#xff0c;为用户提供普通交易、篮子交易、日内回转交易、算法交易、量化投研/回测/实盘等各种交易工具&#xff0c;满足用户的各种交易需求和交易场景&#xff0c;帮助用户提高交易效率。 运行环境及安装 操作系统&…

OFDM技术概述8——FBMC

Filter bank multicarrier(FBMC&#xff0c;滤波器组多载波)&#xff0c;是一种类似于OFDM的调制方式&#xff0c;用滤波器抑制子载波的旁瓣大小&#xff0c;使用FFT/IFFT或多相滤波器实现&#xff0c;其应用于5G的主要优势&#xff1a; 子载波信号带限&#xff0c;带外泄漏小…

5.(vue3.x+vite)水平垂直居中实现方式

前端技术社区总目录(订阅之前请先查看该博客) 示例效果 介绍 (1)父级元素设置position:relative; 子级元素设置:position:absolute;left:50%;top:50%;transform: translate(-50%,-50%); 兼容性较好 (1)父级元素设置弹性盒子:display:flex;justify-content:center; a…

LabVIEW幅频特性测试系统

使用LabVIEW软件开发的幅频特性测试系统。该系统整合了Agilent 83732B信号源与Agilent 8563EC频谱仪&#xff0c;通过LabVIEW编程实现自动控制和数据处理&#xff0c;提供了成本效益高、操作简便的解决方案&#xff0c;有效替代了昂贵的专用仪器&#xff0c;提高了测试效率和设…

library source does not match the bytecode for class SpringApplication

library source does not match the bytecode for class SpringApplication 问题描述&#xff1a;springboot源码点进去然后download source后提示标题内容。spring版本5.2.8.RELEASE&#xff0c;springboot版本2.7.18 解决方法&#xff1a;把spring版本改为与boot版本对应的6.…

如何快速去除视频里面的水印字幕等信息?(内附工具)

环境&#xff1a; VSR 需要独显 GPU:N 4070TI 12G 问题描述&#xff1a; 如何快速去除视频里面的水印字幕等信息&#xff1f; 解决方案&#xff1a; 1.打开AI工具VSR&#xff0c;打了要处理的视频 2.右侧滑块调整绿色选框&#xff0c;选中要去的字幕或者水印 这次测试右…