【JavaEE初阶】 定时器详解与实现

news2024/11/19 10:43:22

文章目录

  • 🌴定时器是什么
  • 🎋Java标准库中的定时器
  • 🌲模拟实现定时器
    • 🚩定时器的构成
    • 📌第一步:MyStack类的建立
    • 📌第二步:创建MyTimer类
    • 📌第三步:解决相关问题
  • 🌳完整代码实现与测试
  • ⭕总结

🌴定时器是什么

在这里插入图片描述
定时器也是软件开发中的一个重要组件. 类似于一个 “闹钟”. 达到一个设定的时间之后, 就执行某个指定好的代码

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

🎋Java标准库中的定时器

  • 标准库中提供了一个 Timer 类. Timer 类的核心方法为 schedule .

  • schedule 包含两个参数.

  • 第一个参数指定即将要执行的任务代码,

  • 第二个参数指定多长时间之后执行 (单位为毫秒)

代码示例:

下面程序分别有一个定时器,设置了三个不同的时间

import java.util.Timer;
import java.util.TimerTask;

public class TestDemo {
    public static void main(String[] args) {
        Timer timer = new Timer();
        System.out.println("程序启动!");
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("定时器3");
            }
        },3000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("定时器2");
            }
        },2000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("定时器1");
            }
        },1000);
    }
}

运行结果如下:
在这里插入图片描述
结果如我们所示,按照时间顺序进行打印

🌲模拟实现定时器

首先我们先来看一下定时器的构成

🚩定时器的构成

  1. 一个带优先级的阻塞队列
  • 为啥要带优先级呢?
    因为阻塞队列中的任务都有各自的执行时刻 (delay). 最先执行的任务一定是 delay 最小的. 使用带优先级的队列就可以高效的把这个 delay 最小的任务找出来.
  1. 队列中的每个元素是一个 MyTask 对象.

  2. MyTask 中带有一个时间属性, 队首元素就是即将要执行的任务

  3. 同时有一个线程一直扫描队首元素, 看队首元素是否需要执行

📌第一步:MyStack类的建立

包含两个属性

  • 包含一个 Runnable 对象

  • 一个 time(毫秒时间戳)

由于我们的MyTask类需要放入一个带优先级的阻塞队列中,所以我们需要MyTack可以比较,这里博主选择重写 Comparable 接口里的compareTo方法

代码实现如下:

public class MyTask implements Comparable<MyTask> {
    private Runnable runnable;
    private  long time;

    public MyTask() {
        System.out.println(1);
    }
    public void tad() {
        System.out.println(2);
    }
    public MyTask(Runnable runnable, long time) {
        this.runnable = runnable;
        this.time = time;
    }
    public long gettime(MyTask) {
        return this.time;
    }
    //执行任务
    public void run() {
        runnable.run();
    }
    @Override
    public int compareTo(MyTask o) {
        return (int)(this.time - o.time);
    }
}

📌第二步:创建MyTimer类

该类需要有一个带有优先级的阻塞队列

还需要有一个可schedule 方法用于我们来插入我们我们需要执行的任务

public class MyTimer {
    // 有一个阻塞优先级队列, 来保存任务.
    private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();

    // 指定两个参数
    // 第一个参数是 任务 内容
    // 第二个参数是 任务 在多少毫秒之后执行. 形如 1000
    public void schedule(Runnable runnable, long after) {
        // 注意这里的时间上的换算
        MyTask task = new MyTask(runnable, System.currentTimeMillis() + after);
        queue.put(task);
    }
}
  • 由于我们输入的都是一个时间大小,所以我们需要进行处理一下,

  • 这里的System.currentTimeMillis()是获取当时的时间戳

  • 再加上所需要的时间大小即达到我们效果

其次还需要一个线程循环扫描

  • 该扫描我们需要做的是

  • 取出队首元素, 检查看看队首元素任务是否到时间了.

  • 如果时间没到, 就把任务塞回队列里去.

  • 如果时间到了, 就把任务进行执行.

    private Thread t = null;
    public MyTimer() {
        t = new Thread() {
            @Override
            public void run() {
                while (true) {
                    try {
                        // 取出队首元素, 检查看看队首元素任务是否到时间了.
                        // 如果时间没到, 就把任务塞回队列里去.
                        // 如果时间到了, 就把任务进行执行.
                        MyTask myTask = queue.take();
                        long curTime = System.currentTimeMillis();
                        if (curTime < myTask.getTime()) {
                            // 还没到点, 先不必执行
                            // 现在是 13:00, 取出来的任务是 14:00 执行
                            //塞回去
                            queue.put(myTask);
                        } else {
                            // 时间到了!! 执行任务!!
                            myTask.run();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        //启动线程
        t.start();
    }

📌第三步:解决相关问题

  • 问题一:while (true) 转的太快了, 造成了无意义的 CPU 浪费.

比如第一个任务设定的是 1 min 之后执行某个逻辑. 但是这里的 while (true) 会导致每秒钟访问队首元素几万次.

解决办法:引入一个locker对象, 借助该对象的 wait / notify 来解决 while (true) 的忙等问题.

我们在循环扫描里:引入 wait, 等待一定的时间.并修改 MyTimer 的 schedule 方法, 每次有新任务到来的时候唤醒一下循环扫描线程. (因为新插入的任务可能是需要马上执行的)

  • 问题二:原子性问题

由于我们的出队列操作和判断语句不具有原子性

问题情况如下:

出队列操作拿到任务后,还没有进行判断

然后这时候有一个来了一个新任务
在这里插入图片描述
但是此时我们该任务还没有wait()操作,而且我们由于添加新元素,notify()操作已执行,这就导致后面的wait操作不会被唤醒,那么新来的任务就在相应时间来没有被执行

解决方法:将出队列操作与判断操作都加上锁

代码实现如下:

import java.util.concurrent.PriorityBlockingQueue;

public class MyTimer {
    // 有一个阻塞优先级队列, 来保存任务.
    private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
    // 扫描线程
    private Thread t = null;
    private Object locker = new Object();
    public MyTimer() {
        t = new Thread() {
            @Override
            public void run() {
                while (true) {
                    try {
                        // 取出队首元素, 检查看看队首元素任务是否到时间了.
                        // 如果时间没到, 就把任务塞回队列里去.
                        // 如果时间到了, 就把任务进行执行.
                        synchronized (locker) {
                            MyTask myTask = queue.take();
                            long curTime = System.currentTimeMillis();
                            if (curTime < myTask.getTime()) {
                                // 还没到点, 先不必执行
                                // 现在是 13:00, 取出来的任务是 14:00 执行
                                queue.put(myTask);
                                // 在 put 之后, 进行一个 wait
                                locker.wait(myTask.getTime() - curTime);
                            } else {
                                // 时间到了!! 执行任务!!
                                myTask.run();
                            }
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t.start();
    }
    // 指定两个参数
    // 第一个参数是 任务 内容
    // 第二个参数是 任务 在多少毫秒之后执行. 形如 1000
    public void schedule(Runnable runnable, long after) {
        // 注意这里的时间上的换算
        MyTask task = new MyTask(runnable, System.currentTimeMillis() + after);
        queue.put(task);
        synchronized (locker) {
            locker.notify();
        }
    }
}

🌳完整代码实现与测试

计时器完整代码:

import java.util.concurrent.PriorityBlockingQueue;

class MyTask implements Comparable<MyTask> {
    private Runnable runnable;
    private  long time;

    public MyTask() {
        System.out.println(1);
    }
    public void tad() {
        System.out.println(2);
    }
    public MyTask(Runnable runnable, long time) {
        this.runnable = runnable;
        this.time = time;
    }
    public long getTime() {
        return this.time;
    }
    //执行任务
    public void run() {
        runnable.run();
    }
    @Override
    public int compareTo(MyTask o) {
        return (int)(this.time - o.time);
    }
}

public class MyTimer {
    // 有一个阻塞优先级队列, 来保存任务.
    private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
    // 扫描线程
    private Thread t = null;
    private Object locker = new Object();
    public MyTimer() {
        t = new Thread() {
            @Override
            public void run() {
                while (true) {
                    try {
                        // 取出队首元素, 检查看看队首元素任务是否到时间了.
                        // 如果时间没到, 就把任务塞回队列里去.
                        // 如果时间到了, 就把任务进行执行.
                        synchronized (locker) {
                            MyTask myTask = queue.take();
                            long curTime = System.currentTimeMillis();
                            if (curTime < myTask.getTime()) {
                                // 还没到点, 先不必执行
                                // 现在是 13:00, 取出来的任务是 14:00 执行
                                queue.put(myTask);
                                // 在 put 之后, 进行一个 wait
                                locker.wait(myTask.getTime() - curTime);
                            } else {
                                // 时间到了!! 执行任务!!
                                myTask.run();
                            }
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t.start();
    }
    // 指定两个参数
    // 第一个参数是 任务 内容
    // 第二个参数是 任务 在多少毫秒之后执行. 形如 1000
    public void schedule(Runnable runnable, long after) {
        // 注意这里的时间上的换算
        MyTask task = new MyTask(runnable, System.currentTimeMillis() + after);
        queue.put(task);
        synchronized (locker) {
            locker.notify();
        }
    }
}

测试代码如下

public class TestDemo2 {
    public static void main(String[] args) {
        MyTimer myTimer = new MyTimer();
        System.out.println("程序启动");
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("计时器3");
            }
        },3000);
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("计时器2");
            }
        },2000);
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("计时器1");
            }
        },1000);
    }
}

测试结果如下:
在这里插入图片描述

⭕总结

关于《【JavaEE初阶】 定时器详解与实现》就讲解到这儿,感谢大家的支持,欢迎各位留言交流以及批评指正,如果文章对您有帮助或者觉得作者写的还不错可以点一下关注,点赞,收藏支持一下!

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

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

相关文章

ant利用pathconvert将资源集合转换为路径方式

ant可以利用pathconvert 任务&#xff0c;将内嵌的资源集合转换为某一平台的路径方式。pathconvert可以通过属性property设置一个属性名称&#xff0c;将转换后的路径保存到属性中。 例如&#xff0c;下面的代码&#xff1a; <project name"demo_project"><…

2023年中国一次性医用内窥镜市场发展现状分析:相关产品进入上市高峰期[图]

基于对减少交叉感染风险和维护成本的需求等因素&#xff0c;一种新兴的、耗材化的一次性内窥镜可以避免因重复使用产品而导致的感染问题和高额的清洗消毒费用&#xff0c;从而提高患者的安全性并帮助医疗机构节省运营成本。 一次性和可重复使用医用内窥镜特点对比 资料来源&am…

C++前缀和算法:合并石头的最低成本原理、源码及测试用例

本文涉及的基础知识点 C算法&#xff1a;前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频 动态规划&#xff0c;日后完成。 题目 有 n 堆石头排成一排&#xff0c;第 i 堆中有 stones[i] 块石头。 每次 移动 需要将 连续的 k 堆石头合并为一堆&#xff0c;而…

asp.net企业招聘管理系统VS开发sqlserver数据库web结构c#编程Microsoft Visual Studio计算机毕业设计

一、源码特点 asp.net 企业招聘管理系统 是一套完善的web设计管理系统&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为vs2010&#xff0c;数据库为sqlserver2008&#xff0c;使用c#语 言开发 asp.net企业招聘管理系统 二、功…

SpringBoot常见异步编程,你会多少?

微信公众号访问地址&#xff1a;SpringBoot常见异步编程&#xff0c;你会多少&#xff1f; 近期热推文章&#xff1a; 1、springBoot对接kafka,批量、并发、异步获取消息,并动态、批量插入库表; 2、SpringBoot用线程池ThreadPoolTaskExecutor异步处理百万级数据; 3、基于Redis…

线性代数2:梯队矩阵形式

图片来自 Europeana on Unsplash 一、前言 欢迎阅读的系列文章的第二篇文章&#xff0c;内容是线性代数的基础知识&#xff0c;线性代数是机器学习背后的基础数学。在我之前的文章中&#xff0c;我介绍了线性方程和系统、矩阵符号和行缩减运算。本文将介绍梯队矩阵形式&#xf…

2023年中国养殖渔船产业链、市场规模及发展趋势分析[图]

养殖渔船行业是指涉及水产养殖活动的渔船制造、运营和相关服务的产业。这个行业将渔船和水产养殖业结合起来&#xff0c;包括生产和维护用于养殖水域中养殖活动的各种船只&#xff0c;如养殖网船、渔业养殖船、水产养殖工作船等。 养殖渔船行业产业链 资料来源&#xff1a;共研…

2023年中国车用冲压模具行业特征、竞争现状及行业市场规模分析[图]

汽车冲压件模具具有尺寸大、型面复杂、精度要求高等特点&#xff0c;属于技术密集型产品。汽车冲压模具能快速精密地把材料直接加工成零件或半成品并通过焊接、铆接、拼装等工艺装配成零部件&#xff0c;冲压模具的设计开发和加工能力对汽车冲压零部件产品总制造成本、质量及性…

SpringBoot(二)集成 Quartz:2.5.4

Quartz是一个广泛使用的开源任务调度框架&#xff0c;用于在Java应用程序中执行定时任务和周期性任务。它提供了强大的调度功能&#xff0c;允许您计划、管理和执行各种任务&#xff0c;从简单的任务到复杂的任务。 以下是Quartz的一些关键特点和功能&#xff1a; 灵活的调度器…

海外问卷调查是不是真的能赚钱?

海外问卷调查是不是真的能赚钱&#xff1f;我来告诉你&#xff0c;我在橙河网络这家公司干了两年半的问卷调查&#xff0c;可以明确地告诉你&#xff1a;海外问卷调查确实可以赚钱&#xff0c;真的&#xff01; 海外问卷调查这个项目&#xff0c;在国内已经存在了很长时间&…

KVM动态在线迁移实操笔录

环境介绍 一台NFS&#xff08;192.168.184.132&#xff09; 一台KVM-a&#xff08;192.168.184.133&#xff09; 一台KVM-b&#xff08;192.168.184.134&#xff09; NFS配置 [rootlocalhost ~]# setenforce 0 //关闭selinux [rootlocalhost ~]# service iptables stop [root…

电子元器件网络变压器(网络滤波器 ̖ 脉冲变压器)的EMI产生原因

Hqst华强盛&#xff08;盈盛电子&#xff09;导读&#xff1a;网络变压器&#xff08;网络滤波器 ̖ 脉冲变压器&#xff0c;以下称网络变压器&#xff09;在工作过程中会产生电磁场&#xff0c;这可能会导致电磁干扰&#xff08;EMI&#xff09;。EMI会影响设备的性能和可靠性…

基于Java的图书商城管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09; 代码参考数据库参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者&am…

基于Java的图书馆借阅管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09; 代码参考数据库参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者&am…

设计模式:模板模式(C#、JAVA、JavaScript、C++、Python、Go、PHP)

简介&#xff1a; 模板模式&#xff0c;它是一种行为型设计模式&#xff0c;它定义了一个操作中的算法的框架&#xff0c;将一些步骤延迟到子类中实现&#xff0c;使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。 通俗地说&#xff0c;模板模式就是将某一行…

微信小程序数据交互------WXS的使用

&#x1f3ac; 艳艳耶✌️&#xff1a;个人主页 &#x1f525; 个人专栏 &#xff1a;《Spring与Mybatis集成整合》《Vue.js使用》 ⛺️ 越努力 &#xff0c;越幸运。 1.数据库连接 数据表结构&#xff1a; 数据测式&#xff1a; 2.后台配置 pom.xml <?xml version&quo…

重磅发布!RflySim Cloud 智能算法云仿真平台亮相,助力大规模集群算法高效训练

RflySim Cloud智能算法云仿真平台&#xff08;以下简称RflySim Cloud平台&#xff09;是由卓翼智能及飞思实验室为无人平台集群算法验证、大规模博弈对抗仿真、人工智能模型训练等前沿研究领域研发的平台。主要由环境仿真模块、物理效应计算模块、多智能体仿真模块、分布式网络…

Python安装使用graphviz经验,Format: “png“ not recognized

Graphviz 是一款由 AT&T Research 和 Lucent Bell 实验室开源的可视化图形工具&#xff0c;可以很方便的用来绘制结构化的图形网络&#xff0c;支持多种格式输出。Graphviz 输入是一个用 dot 语言编写的绘图脚本&#xff0c;通过对输入脚本的解析&#xff0c;分析出其中的点…

摆闸机的应用领域和性能特点

摆闸机是一种常用于门禁控制和人员管理的设备&#xff0c;它具有以下应用领域和性能特点&#xff1a; 应用领域&#xff1a; 门禁控制&#xff1a;摆闸机可以用于各种场合的门禁控制&#xff0c;如小区、写字楼、学校、医院等。人员管理&#xff1a;摆闸机可以用于管理进出人…