什么是定时器?

  前言👀~

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

定时器

标准库中的定时器

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.mfbz.cn/a/767686.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

帮找Java Bug,面试,项目,解决Java问题

本人是个Java老程序员&#xff0c;如果你有解决不了的问题&#xff0c;或者面试的时候需要人帮助&#xff0c;或者求职就业上任何问题都可以联系我&#xff0c;下面是我微信&#xff0c;欢迎联系我&#xff01;

【JUC】可重入锁理解

可重入锁&#xff08;Reentrant Lock&#xff09;&#xff0c;也称作递归锁&#xff0c;是一种特殊的锁机制&#xff0c;它允许同一个线程多次获取同一个锁。 优点是可以避免死锁。 public class WhatReentrant {public static void main(String[] args) {new Thread(new Runn…

Qt——升级系列(Level Eight):界面优化

目录 QSS 背景介绍 基本语法 QSS设置方式 指定控件样式设置 全局样式设置 从文件加载样式表 使用Qt Designer 编辑样式 选择器 选择器概况 子控件选择器 伪类选择器 样式属性 盒模型 控件样式示例 按钮 复选框、单选框 输入框 列表 菜单栏 登录界面 绘图 基本概念 绘制各种形…

【Kali-linux for WSL】图形化界面安装

文章目录 前言图形化界面安装 前言 之前在WSL中安装了Kali 启动之后发现什么都没有&#xff01;&#xff01;&#xff01; 那我还怎么学习渗透技术&#xff1f;&#xff1f;&#xff1f; 看来&#xff0c;得改进下我的kali-linux for wsl&#xff0c;安装个图形化界面 图形化…

Jetson系列机载电脑创建热点模式配置方法

Jetson nano为例—— 创建热点模式配置方法 1.1、新建一个 WiFi 在屏幕右上角找到网络图标&#xff0c;点击后选择“Edit Connections”选项&#xff0c;进入选择网络连接页面&#xff0c;然后点击左下角加号&#xff0c;新建一个连接&#xff0c;类型选择 WiFi 后点击 “cre…

AI降重,不再难:降AI率的实用技巧大揭秘

如何有效降低AIGC论文的重复率&#xff0c;也就是我们说的aigc如何降重&#xff1f;AIGC疑似度过高确实是个比较愁人的问题。如果你用AI帮忙写了论文&#xff0c;就一定要在交稿之前做一下AIGC降重的检查。一般来说&#xff0c;如果论文的AIGC超过30%&#xff0c;很可能会被判定…

剪画小程序:如何将视频变成自己的作品!在手机上这样做就可以了!

亲爱的小伙伴们&#xff0c;我是你们的博主小画&#xff01; 今天和大家分享两个在视频剪辑中实用的技巧—旋转视频和添加水印&#xff01; 在我们的创作过程中&#xff0c;有时候常规的视角并不能完全展现出视频的魅力和创意。而通过旋转视频&#xff0c;就能为观众带来全新的…

【云原生监控】Prometheus 普罗米修斯从搭建到使用详解

目录 一、前言 二、服务监控概述 2.1 什么是微服务监控 2.2 微服务监控指标 2.3 微服务监控工具 三、Prometheus概述 3.1 Prometheus是什么 3.2 Prometheus 特点 3.3 Prometheus 架构图 3.3.1 Prometheus核心组件 3.3.2 Prometheus 工作流程 3.4 Prometheus 应用场景…

新规则!2024年信息素养大赛复赛图形化编程题这么写系统才能通过

2024年全国青少年信息素养大赛复赛即将在7月6日陆续开赛&#xff0c;今年Scratch图形化编程小低组和小高组分别为6道编程题&#xff0c;将采用新的判题规则&#xff0c;类似GESP的OJ系统判题&#xff0c;主要有以下三个方面&#xff1a; 1、变量名大小写要和题目完全一致。 2…

数据万象推出智能检索MetaInsight,现已开启限时公测

海量文件的分析统计一直是对象存储COS的热点需求&#xff0c;伴随AIGC飞速迭代发展&#xff0c;在众多不同模态素材的海洋中&#xff0c;用户也急需更高效地管理和利用多媒体内容&#xff0c;打破传统搜索的桎梏。 数据万象推出的智能检索 MetaInsight 服务将多模态检索与元数…

开源之夏|祝贺MatrixOne开源社区项目中选同学!

在本届「开源之夏 2024」活动中&#xff0c;MatrixOne开源社区共计上线3个项目任务&#xff0c;最终有 3位同学成功突围。接下来让我们看看每个项目的详细中选情况&#xff1a; 中选学生公示 项目名称&#xff1a;基于大语言模型的操作系统任务自动识别&#xff0c;拆解&#…

Unicode 和 UTF-8 以及它们之间的关系

通俗易懂的 Unicode 和 UTF-8 解释 Unicode 是什么&#xff1f; 想象一下&#xff0c;我们有一个巨大的图书馆&#xff0c;这个图书馆里有各种各样的书&#xff0c;每本书都有一个唯一的编号。Unicode 就像是这个图书馆的目录系统&#xff0c;它给世界上所有的字符&#xff0…

数据要素如何转化为生产力?关键在以指标为中心实现数据分析“快全准省”

进入到数字化时代&#xff0c;企业的经营变得越来越精细化、智能化、自动化&#xff0c;其中&#xff0c;数据就变成了关键要素&#xff0c;在企业的业务运营、经营决策、流程改进、创新驱动、资源配置等方面发挥出巨大作用。 数据具体如何转化为生产力&#xff0c;有几个关键…

俄罗斯ozon运费计算工具,跨境电商ozon物流运费计算工具

OZON平台服装类目卖家而言&#xff0c;如何快速、准确地为产品定价&#xff0c;并有效管理运费成本&#xff0c;直接关系到市场竞争力与利润空间。接下来我们看看俄罗斯ozon运费计算工具&#xff0c;跨境电商ozon物流运费计算工具。 萌啦Ozon定价工具&#xff1a;智能模拟&…

OCR text detect

主干网络 VoVNet&#xff1a;实时目标检测的新backbone网络_vovnet pytorch-CSDN博客 DenseNet&#xff1a; arxiv.org/pdf/1608.06993 密集连接&#xff1a; DenseNet 的核心思想是将网络中的每一层与其前面的所有层直接连接。对于一个 L 层的网络&#xff0c;DenseNet 具有…

【深度学习】扫描全能王的AI驱动创新与智能高清滤镜技术解析

目录 引言1、扫描全能王2、智能高清滤镜黑科技2.1、图像视觉矫正2.2、去干扰技术 3、实际应用案例3.1、打印文稿褶皱检测3.2、试卷擦除手写3.3、老旧文件处理3.4、收银小票3.5、从不同角度扫描文档 4、用户体验结论与未来展望 引言 在数字化时代背景下&#xff0c;文档扫描功能…

AI是在帮助开发者还是取代他们

目录 1.概述 1.1.AI助力开发者 1.2.AI对开发者的挑战 2.AI工具现状 2.1. GitHub Copilot 2.2. TabNine 2.3.小结 3.AI对开发者的影响 3.1.对开发者的影响 3.2.开发者需要掌握的新技能 3.3.在AI辅助的环境中保持竞争力的策略 4.AI开发的未来 5.总结 1.概述 生成式…

Git学习(常用的一些命令)

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;日常聊聊 ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 正文 配置相关&#xff1a; 创建与克隆仓库&#xff1a; 基本操作&#xff1a; 分支操作&#xff1a; 远程仓库操作&#xff1a…

使用Adobe Acrobat对PDF文档进行数字签名

文章目录 前言一、使用Adobe Acrobat对PDF文档进行数字签名1.使用Adobe Acrobat打开需要进行签名的PDF文档2. 点击【查看更多】3.点击【使用证书】4.点击【数字签名】5.使用鼠标选定一个区域6.选择您需要使用的证书 → 点击【继续】7.点击【签名】8.签名成功 前言 一、使用Ado…

App渗透:BurpSuite插件-Brida apk逆向自动加解密Custom plugins演示

文章目录 前言Eureka 前言 【App渗透:BurpSuite插件-Brida apk逆向自动加解密Custom plugins演示】 很久很久之前&#xff0c;更新过Brida的安装和hook脚本的调试&#xff0c;今晚终于更新了Brida的核心功能&#xff0c;自动加解密。视频里演示的app是我自己开发的&#xff0c…