JavaEE之定时器及自我实现

在生活当中,有很多事情,我们不是立马就去做,而是在规定了时间之后,在到该时间时,再去执行,比如:闹钟、定时关机等等,在程序的世界中,有些代码也不是立刻执行,那么我们该如何实现呢?一探究竟——>《定时器》

1. 定时器

定时器是什么

定时器也是软件开发中的⼀个重要组件.类似于⼀个"闹钟".达到⼀个设定的时间之后,就执行某个指定好的代码.
定时器是⼀种实际开发中非常常用的组件.
比如网络通信中,如果对方500ms内没有返回数据,则断开连接尝试重连.
比如⼀个Map,希望里面的某个key在3s之后过期(自动删除).
类似于这样的场景就需要用到定时器.

在Java当中也给我们提供了定时器(Timer)的类,请见下文。

标准库中的定时器

  • 标准库中提供了一个Timer类.Timer类的核心方法为schedule
  • schedule 包含两个参数.第⼀个参数指定即将要执行的任务代码,第⼆个参数指定多长时间之后执行(单位为毫秒).

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2. 自我实现一个定时器

1.首先定时器是用于处理任务的,我们该如何在定时器当中管理任务呢??

我们通过一个类,描述任务和任务执行的时间
具体任务的逻辑用Runnble表示,执行时间的可以用一个long型delay去表示

/**
 * 任务类
 */
 //由于需要比较时间大小,所以使用接口
class MyTask implements Comparable<MyTask>{

    //任务
    private Runnable runnable = null;
    //延迟时间
    private long time = 0;
    
    public MyTask(Runnable runnable, long delay) {
        //任务不能为空
        if(runnable==null){
            throw new RuntimeException("任务不能为空...");
        }
        //时间不能为负数
        if(delay<0){
            throw new RuntimeException("时间不能为负数...");
        }
        this.runnable = runnable;
        // 计算出任务执行的具体时间
        this.time = delay+System.currentTimeMillis();
    }

    public Runnable getRunnable() {
        return runnable;
    }

    public long getTime() {
        return time;
    }

    //比较当前任务和其他任务的时间
    @Override
    public int compareTo(MyTask o) {
        return (int) (o.getTime()-this.getTime());
    }
}

2.通过MyTask描述了任务之后,由于任务的执行顺序不一样,我们该如何去管理任务呢?

我们通过一个优先级队列把任务的对象组织起来

//用阻塞队列来管理任务
private BlockingQueue<MyTask> queue=new PriorityBlockingQueue<>();

3.我们描述完了任务也通过优先级队列管理了任务对象,我们如何让任务对象和定时器关联起来呢?

    /**
     * 添加任务的方法
     * @param runnable 任务
     * @param delay 延时
     * @throws InterruptedException
     */
    public void schedule(Runnable runnable, long delay) throws InterruptedException {
        // 根据传处的参数,构造一个MyTask
        MyTask task=new MyTask(runnable,delay);
        // 把任务放入阻塞队列
        queue.put(task);
    }
}

4.我们通过schedule方法把任务对象添加到了阻塞队列当中,我们只需要创建一个线程来执行任务即可

此时我们的思路是:创建一个线程不停的扫描任务,取出队列的首元素若时间到就取出执行,时间没到就放回队列不执行,就能写出以下代码:

    // 创建扫描线程
    Thread thread=new Thread(()->{
        //不断的扫描队列中的任务
        while (true){
            try {
                //1.从阻塞队列中获取任务
                MyTask task = queue.take();
                //2.判断到没到执行时间
                long currentTime=System.currentTimeMillis();
                if(currentTime>=task.getTime()){
                    //时间到了就执行任务
                    task.getRunnable().run();
                    }else {
                    // 没有到时间,重新放回队列
                    queue.put(task);
                }
            }catch (InterruptedException e) {
                e.printStackTrace();
            }
        }},"scanThread");
        //启动线程
        thread.start();      

但是上面的代码有一个很明显的问题,就是 “忙等” ,为什么呢?
在这里插入图片描述
那么我们怎么解决这个忙等这个问题呢?

在放回队列时让程序等待一段时间等待一段时间
时间为:下一个任务的执行时间和当前时间的差
那么既然要等待了我们必须要通过持有同一个锁,来完成等待操作,所以我们创建一把锁

修改代码如下:

// 创建扫描线程
Thread thread=new Thread(()->{
    //不断的扫描队列中的任务
    while (true){
        try {
            //1.从阻塞队列中获取任务
            MyTask task = queue.take();
            //2.判断到没到执行任务的时间
            long currentTime=System.currentTimeMillis();
            if(currentTime>=task.getTime()){
                //时间到了就执行任务
                task.getRunnable().run();
            }else {
                // 当前时间与任务执行时间的差
                long waitTime = task.getTime() - currentTime;
                // 没有到时间,重新放回队列
                queue.put(task);
                synchronized (locker){
                    //等时间
                    locker.wait(waitTime);
                }
            }
        }catch (InterruptedException e) {
            e.printStackTrace();
        }
    }},"scanThread");
     //启动线程,真正去系统中申请资源
    thread.start();

通过锁,解决了忙等问题,

5.此时还有一个新的问题,在该队列中若产生了新的任务执行时间在等待任务之前该怎么办呢?

我们在每一次向阻塞队列当中添加新任务时,我们就唤醒一次扫描线程即可

/**
     * 添加任务的方法
     * @param runnable 任务
     * @param delay 延时
     * @throws InterruptedException
     */
    public void schedule(Runnable runnable, long delay) throws InterruptedException {
        // 根据传处的参数,构造一个MyTask
        MyTask task=new MyTask(runnable,delay);
        // 把任务放入阻塞队列
        queue.put(task);
        //在每次添加新任务时,唤醒一次扫描线程,以访扫描线程还在等待,新任务时间过了的问题
        synchronized (locker){
            locker.notifyAll();
        }
    }
}

6.CPU调度的过程中可能会产生执行顺序的问题,或当一个线程执行到一半的时间被调度走的现象,会引发什么问题呢?

在这里插入图片描述

造成该现象的原因是没有保证原子性,我们扩大锁范围即可解决该问题,修改后的代码如下:

//不断的扫描队列中的任务
            while (true){
                synchronized (locker){
                    try {
                        //1.从阻塞队列中获取任务
                        MyTask task = queue.take();
                        //2.判断到没到执行任务的时间
                        long currentTime=System.currentTimeMillis();
                        if(currentTime>=task.getTime()){
                            //时间到了就执行任务
                            task.getRunnable().run();
                        }else {
                            // 当前时间与任务执行时间的差
                            long waitTime = task.getTime() - currentTime;
                            // 没有到时间,重新放回队列
                            queue.put(task);
                            locker.wait(waitTime);
                        }
                    }catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
              
            }

7.由于进入锁之后,MyTask task = queue.take();操作,当阻塞队列中没有元素时,就会阻塞等待,直到队列中有可用元素才继续执行,但是由于MyTask task = queue.take();操作持有了锁,导致无法释放锁,添加任务的方法又迟迟取不到锁,导致一个在等着任务执行,一个在等着获取锁添加任务,造成了“死锁”现象,我们该如何解决呢?

我们发现在为了解决原子性问题时,我们扩大加锁的范围,却又引入了更大的问题
一般我们两害相全取其轻

为了解决无法及时执行任务的问题,我们创建了一个后台的扫描线程,只做定时唤醒操作,定时1s或者任意时间唤醒执行一次


完整的定时器实现代码如下:
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.TimeUnit;

/**
 * 自我实现定时器
 */
public class MyTimer {
    //用阻塞队列来管理任务
    private BlockingQueue<MyTask> queue=new PriorityBlockingQueue<>();
    //创建⼀个锁对象
    private Object locker = new Object();
    public MyTimer() throws InterruptedException {
        // 创建扫描线程
        Thread thread=new Thread(()->{
            //不断的扫描队列中的任务
            while (true){
                synchronized (locker){
                    try {
                        //1.从阻塞队列中获取任务
                        MyTask task = queue.take();
                        //2.判断到没到执行任务的时间
                        long currentTime=System.currentTimeMillis();
                        if(currentTime>=task.getTime()){
                            //时间到了就执行任务
                            task.getRunnable().run();
                        }else {
                            // 当前时间与任务执行时间的差
                            long waitTime = task.getTime() - currentTime;
                            // 没有到时间,重新放回队列
                            queue.put(task);
                            locker.wait(waitTime);
                        }
                    }catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

            }
        },"scanThread");
        //启动线程,真正去系统中申请资源
        thread.start();

        //创建一个后台线程
        Thread daemonThread= new Thread(()->{
            while (true){
                //定时唤醒
                synchronized (locker){
                    locker.notifyAll();
                }
                //休眠一会
                try {
                    TimeUnit.MICROSECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        //设置成后台线程
        daemonThread.setDaemon(true);
        //启动线程
        daemonThread.start();
    }


    /**
     * 添加任务的方法
     * @param runnable 任务
     * @param delay 延时
     * @throws InterruptedException
     */
    public void schedule(Runnable runnable, long delay) throws InterruptedException {
        // 根据传处的参数,构造一个MyTask
        MyTask task=new MyTask(runnable,delay);
        // 把任务放入阻塞队列
        queue.put(task);
        synchronized (locker){
            locker.notifyAll();
        }
    }
}

/**
 * 任务类
 */
class MyTask implements Comparable<MyTask>{

    //任务
    private Runnable runnable = null;
    //延迟时间
    private long time = 0;
    public MyTask(Runnable runnable, long delay) {
        //任务不能为空
        if(runnable==null){
            throw new RuntimeException("任务不能为空...");
        }
        //时间不能为负数
        if(delay<0){
            throw new RuntimeException("时间不能为负数...");
        }
        this.runnable = runnable;
        // 计算出任务执行的具体时间
        this.time = delay+System.currentTimeMillis();
    }


    public Runnable getRunnable() {
        return runnable;
    }


    public long getTime() {
        return time;
    }


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

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

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

相关文章

Qt学习笔记第81到90讲

第81讲 串口调试助手实现自动发送 为这个名叫“定时发送”的QCheckBox编写槽函数。 想要做出定时发送的效果&#xff0c;必须引入QT框架下的毫秒级定时器QTimer&#xff0c;查阅手册了解详情。 在widget.h内添加新的私有成员变量&#xff1a; QTimer *timer; 在widget类的构造…

【LeetCode】力扣刷题热题100道(16-20题)附源码 容器 子数组 数组 连续序列 三数之和(C++)

目录 1.盛最多水的容器 2.和为K的子数组 3.最大子数组和 4.最长连续序列 5.三数之和 1.盛最多水的容器 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴…

AI多模态技术介绍:视觉语言模型(VLMs)指南

本文作者&#xff1a;AIGCmagic社区 刘一手 AI多模态全栈学习路线 在本文中&#xff0c;我们将探讨用于开发视觉语言模型&#xff08;Vision Language Models&#xff0c;以下简称VLMs&#xff09;的架构、评估策略和主流数据集&#xff0c;以及该领域的关键挑战和未来趋势。通…

jenkins入门13--pipeline

Jenkins-pipeline(1)-基础 为什么要使用pipeline 代码&#xff1a;pipeline 以代码的形式实现&#xff0c;通过被捡入源代码控制&#xff0c; 使团队能够编译&#xff0c;审查和迭代其cd流程 可连续性&#xff1a;jenkins 重启 或者中断后都不会影响pipeline job 停顿&#x…

【线性代数】通俗理解特征向量与特征值

这一块在线性代数中属于重点且较难理解的内容&#xff0c;下面仅个人学习过程中的体会&#xff0c;错误之处欢迎指出&#xff0c;有更简洁易懂的理解方式也欢迎留言学习。 文章目录 概念计算几何直观理解意义PS.适用 概念 矩阵本身就是一个线性变换&#xff0c;对一个空间中的…

SQL多表联查、自定义函数(字符串分割split)、xml格式输出

记录一个报表的统计&#xff0c;大概内容如下&#xff1a; 多表联查涉及的报表有&#xff1a;房间表、买家表、合同表、交易表、费用表、修改记录表 注意&#xff1a;本项目数据库使用的是sqlserver&#xff08;mssql&#xff09;&#xff0c;非mysql。 难点1:业主信息&#…

python学opencv|读取图像(三十)使用cv2.getAffineTransform()函数倾斜拉伸图像

【1】引言 前序已经学习了如何平移和旋转缩放图像&#xff0c;相关文章链接为&#xff1a; python学opencv|读取图像&#xff08;二十七&#xff09;使用cv2.warpAffine&#xff08;&#xff09;函数平移图像-CSDN博客 python学opencv|读取图像&#xff08;二十八&#xff0…

C语言数据结构与算法(排序)详细版

大家好&#xff0c;欢迎来到“干货”小仓库&#xff01;&#xff01; 很高兴在CSDN这个大家庭与大家相识&#xff0c;希望能在这里与大家共同进步&#xff0c;共同收获更好的自己&#xff01;&#xff01;无人扶我青云志&#xff0c;我自踏雪至山巅&#xff01;&#xff01;&am…

【竞技宝】CS2:HLTV2024选手排名TOP4-NiKo

北京时间2025年1月11日,HLTV年度选手排名正在持续公布中,今日凌晨正式公布了今年的TOP4选手为G2(目前已转为至Falcons)战队的NiKo。 选手简介 NiKo是一名来自波黑的CS职业选手,现年26岁。作为DOTA2饱负盛名的职业选手,NiKo在CS1.6时代就已经开始征战职业赛场。2012年,年仅15岁…

IOS界面传值-OC

1、页面跳转 由 ViewController 页面跳转至 NextViewController 页面 &#xff08;1&#xff09;ViewController ViewController.h #import <UIKit/UIKit.h>interface ViewController : UIViewControllerend ViewController.m #import "ViewController.h" …

树的模拟实现

一.链式前向星 所谓链式前向星&#xff0c;就是用链表的方式实现树。其中的链表是用数组模拟实现的链表。 首先我们需要创建一个足够大的数组h&#xff0c;作为所有结点的哨兵位。创建两个足够大的数组e和ne&#xff0c;一个作为数据域&#xff0c;一个作为指针域。创建一个变…

【ArcGIS微课1000例】0138:ArcGIS栅格数据每个像元值转为Excel文本进行统计分析、做图表

本文讲述在ArcGIS中,以globeland30数据为例,将栅格数据每个像元值转为Excel文本,便于在Excel中进行统计分析。 文章目录 一、加载globeland30数据二、栅格转点三、像元值提取至点四、Excel打开一、加载globeland30数据 打开配套实验数据包中的0138.rar中的tif格式栅格土地覆…

Redis集群模式下主从复制和哨兵模式

Redis主从复制是由一个Redis服务器或实例(主节点)来控制一个Redis服务器或实例(从节点),从节点从主节点获取数据更新数据 集群模式下主从数据复制过程 从服务器连接到主服务器,发送SYNC命令。主服务器接收到SYNC命令后,开始执行BGSAVE命令生成RDB文件。主服务器BGSAVE执…

高难度下的一闪---白金ACT游戏设计思想的一点体会

1、以前光环的开发者好像提出过一个理论&#xff0c;大意是游戏要让玩家保持30秒的循环&#xff0c; 持续下去。大意跟后来的心流接近。 2、根据我自身的开发体会&#xff0c;想要保持正回路&#xff0c;并不容易。 一个是要保持适当的挑战性&#xff0c;毫无难度的低幼式玩法…

页面滚动下拉时,元素变为fixed浮动,上拉到顶部时恢复原状,js代码以视频示例

页面滚动下拉时,元素变为fixed浮动js代码 以视频示例 <style>video{width:100%;height:auto}.div2,#float1{position:fixed;_position:absolute;top:45px;right:0; z-index:250;}button{float:right;display:block;margin:5px} </style><section id"abou…

算法题(32):三数之和

审题&#xff1a; 需要我们找到满足以下三个条件的所有三元组&#xff0c;并存在二维数组中返回 1.三个元素相加为0 2.三个元素的下标不可相同 3.三元组的元素不可完全相同 思路&#xff1a; 混乱的数据不利于进行操作&#xff0c;所以我们先进行排序 我们可以采取枚举的方法进…

科研绘图系列:R语言绘制Y轴截断分组柱状图(y-axis break bar plot)

禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍特点意义加载R包数据下载导入数据数据预处理画图输出总结系统信息介绍 Y轴截断分组柱状图是一种特殊的柱状图,其特点是Y轴的刻度被截断,即在某个范围内省略了部分刻度。这种图表…

PHP民宿酒店预订系统小程序源码

&#x1f3e1;民宿酒店预订系统 基于ThinkPHPuniappuView框架精心构建的多门店民宿酒店预订管理系统&#xff0c;能够迅速为您搭建起专属的、功能全面且操作便捷的民宿酒店预订小程序。 该系统不仅涵盖了预订、退房、WIFI连接、用户反馈、周边信息展示等核心功能&#xff0c;更…

前端 图片上鼠标画矩形框,标注文字,任意删除

效果&#xff1a; 页面描述&#xff1a; 对给定的几张图片&#xff0c;每张能用鼠标在图上画框&#xff0c;标注相关文字&#xff0c;框的颜色和文字内容能自定义改变&#xff0c;能删除任意画过的框。 实现思路&#xff1a; 1、对给定的这几张图片&#xff0c;用分页器绑定…

shell练习

1、shell 脚本写出检测 /tmp/size.log 文件如果存在显示它的内容&#xff0c;不存在则创建一个文件将创建时间写入。 2、写一个 shel1 脚本,实现批量添加 20个用户,用户名为user01-20,密码为user 后面跟5个随机字符。 3、编写个shel 脚本将/usr/local 日录下大于10M的文件转移…