JavaEE之定时器及自我实现

news2025/1/12 13:23:38

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

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());
    }
}

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

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

相关文章

深入Android架构(从线程到AIDL)_23 活用IBinder接口于近程通信01

1、 在同一进程里活用IBinder接口 议题 1. myActivity对象是谁创建的呢? 2. myService对象是谁创建的呢? 3. 当myService类里有个f1()函数&#xff0c;如何去调用它呢? 4. 必须先取得myService对象的指针&#xff0c;才能调用f1()函数去存取对象的属性(Attribute)值。 …

vue3后台系统动态路由实现

动态路由的流程&#xff1a;用户登录之后拿到用户信息和token&#xff0c;再去请求后端给的动态路由表&#xff0c;前端处理路由格式为vue路由格式。 1&#xff09;拿到用户信息里面的角色之后再去请求路由表&#xff0c;返回的路由为tree格式 后端返回路由如下&#xff1a; …

如何开启苹果手机(IOS)系统的开发者模式?

如何开启开发者模式&#xff1f; 一、打开设置二、隐私与安全性三、找到开发者模式四、开启开发者模式------------------------------------------------------------如果发现没有开发者模式的选项一、电脑下载爱思助手二、连接手机三、工具箱——虚拟定位——打开虚拟定位——…

国产编辑器EverEdit - 扩展脚本:在当前文件目录下新建同类型文件

1 扩展脚本&#xff1a;在当前文件目录下新建同类型文件 1.1 应用场景 用户在进行编程语言学习时&#xff0c;比如&#xff1a;Python&#xff0c;经常做完一个小练习后&#xff0c;又需要新建一个文件&#xff0c;在新建文件的时候&#xff0c;不但要选择文件类型&#xff0c…

011:利用大津算法完成图片分割

本文为合集收录&#xff0c;欢迎查看合集/专栏链接进行全部合集的系统学习。 合集完整版请参考这里。 上一篇文章介绍了大津算法可以完成图片的前景和背景分割。 总的来说&#xff0c;大津算法的核心思想就两个&#xff1a; 数学上&#xff0c;通过确定一个像素阈值&#xf…

Jenkins触发器--在其他项目执行后构建

前言&#xff1a; jenkins中有多种触发器可用&#xff0c;可以方便的控制构建的启动 这里简单介绍下项目后构建的配置方法 1. 解释&#xff1a; Build after other projects are built Set up a trigger so that when some other projects finish building, a new build is…

PowerApps助力PowerBI实现数据写回

原文发布日期: 2019-08-01 06:03:50 0000 注&#xff1a;本文旨在介绍Power BI如何利用PowerApps实现用户在前端对数据源进行增删查改&#xff0c;关于此&#xff0c;你也可以在Google上找到更详细但较零散的资料 正文 在SSAS多维数据集中&#xff0c;开发者可以给数据开启&q…

oracle 19c安装

文章目录 一 环境配置1、更换yum源2、文件配置 二 oracle环境配置1、下载依赖包2、创建用户和用户组3、创建目录并赋予权限4、修改资源限制参数5、修改内核参数6、配置安全7、配置Oracle环境变量 三 安装Oracle数据库四 创建Oracle实例五 启动数据库 一 环境配置 1、更换yum源…

LabVIEW启动时Access Violation 0xC0000005错误

问题描述 在启动LabVIEW时&#xff0c;可能出现程序崩溃并提示以下错误&#xff1a;Error 0xC0000005 (Access Violation) ​ Access Violation错误通常是由于权限不足、文件冲突或驱动问题引起的。以下是解决此问题的全面优化方案&#xff1a; 解决步骤 1. 以管理员身份运行…

xilinx平台使用多个 FIFO 拼接

Xilinx FIFO IP 输入 的最大位宽 是 1024 bit &#xff0c;当需要缓存的数据是 1280bit 又或者是 1536等 。怎么办呢&#xff1f; 有一个办法就是拆数据&#xff0c;将1280拆5个 256bit输入&#xff0c;也就是可以使用 5个 256位宽输入的FIFO拼接起来。&#xff08;其它位宽也…

Ceph分布式存储集群,不仅仅是一个简单的对象存储解决方案

Ceph 作为 OpenStack 的存储后端 块存储&#xff08;Cinder 后端&#xff09; Ceph 的 RBD&#xff08;RADOS Block Device&#xff09;模块作为 OpenStack Cinder 服务的后端&#xff0c;为虚拟机提供块级别的存储资源。RBD 支持快照、克隆和恢复等功能&#xff0c;能够满足虚…

SD ComfyUI工作流 老照片修复上色

文章目录 老照片修复上色SD模型Node节点工作流程开发与应用效果展示老照片修复上色 该工作流专门设计用于老照片的修复和上色,通过一系列高级的图像处理技术,包括深度图预处理、面部修复、上色和图像放大等步骤,来恢复老照片的质量并增加色彩。首先,工作流加载老照片并进行…

Jmeter-压测时接口如何按照顺序执行

Jmeter-压测时接口如何按照顺序执行-临界部分控制器 在进行压力测试时&#xff0c;需要按照顺序进行压测&#xff0c;比如按照接口1、接口2、接口3、接口4 进行执行 查询结果是很混乱的&#xff0c;如果请求次数少&#xff0c;可能会按照顺序执行&#xff0c;但是随着次数增加…

Mysql--运维篇--日志管理(连接层,SQL层,存储引擎层,文件存储层)

MySQL提供了多种日志类型&#xff0c;用于记录不同的活动和事件。这些日志对于数据库的管理、故障排除、性能优化和安全审计非常重要。 一、错误日志 (Error Log) 作用&#xff1a; 记录MySQL服务器启动、运行和停止期间遇到的问题和错误信息。 查看&#xff1a; 默认情况下…

【2025 Rust学习 --- 13 闭包:Rust的Lambda】

Rust的Lambda — 闭包 对整型向量进行排序很容易&#xff1a; integers.sort(); 遗憾的是&#xff0c;当我们想对一些数据进行排序时&#xff0c;它们几乎从来都不是整型向量。例 如&#xff0c;对某种记录型数据来说&#xff0c;内置的 sort 方法一般不适用&#xff1a; st…

鸿蒙面试 2025-01-09

鸿蒙分布式理念&#xff1f;&#xff08;个人认为理解就好&#xff09; 鸿蒙操作系统的分布式理念主要体现在其独特的“流转”能力和相关的分布式操作上。在鸿蒙系统中&#xff0c;“流转”是指涉多端的分布式操作&#xff0c;它打破了设备之间的界限&#xff0c;实现了多设备…

一个基于Spring Boot的智慧养老平台

以下是一个基于Spring Boot的智慧养老平台的案例代码。这个平台包括老人信息管理、健康监测、紧急呼叫、服务预约等功能。代码结构清晰&#xff0c;适合初学者学习和参考。 1. 项目结构 src/main/java/com/example/smartelderlycare├── controller│ ├── ElderlyCon…

Taro+react 开发第一节创建 带有redux状态管理的项目

Taro 项目基于 node&#xff0c;请确保已具备较新的 node 环境&#xff08;>16.20.0&#xff09;&#xff0c;推荐使用 node 版本管理工具 nvm 来管理 node&#xff0c;这样不仅可以很方便地切换 node 版本&#xff0c;而且全局安装时候也不用加 sudo 了。 1.安装 npm inf…

云商城--基础数据处理和分布式文件存储

第2章 基础数据处理和分布式文件存储 1.分布式文件存储系统Ceph学习 ​ 1).掌握Ceph架构 ​ 2).掌握Ceph组件 ​ 3).搭建Ceph集群(了解) 2.Ceph使用 ​ 1).基于Ceph实现文件上传 ​ 2).基于Ceph实现文件下载 3.SKU、SPU管理 ​ 1).掌握SKU和SPU关系 ​ 2).理解商品发…

Vue.js:现代前端开发的灵活框架

大家好&#xff01;我是 [数擎 AI]&#xff0c;一位热爱探索新技术的前端开发者&#xff0c;在这里分享前端和 Web3D、AI 技术的干货与实战经验。如果你对技术有热情&#xff0c;欢迎关注我的文章&#xff0c;我们一起成长、进步&#xff01; 开发领域&#xff1a;前端开发 | A…