[JavaEE]定时器

news2024/9/22 21:33:59


专栏简介: JavaEE从入门到进阶

题目来源: leetcode,牛客,剑指offer.

创作目标: 记录学习JavaEE学习历程

希望在提升自己的同时,帮助他人,,与大家一起共同进步,互相成长.

学历代表过去,能力代表现在,学习能力代表未来! 


目录: 

1.定时器的概念

2.标准库中的定时器

3.实现定时器

3.1定时期的构成:

3.2 实现步骤:


1.定时器的概念

定时器类似于一个"闹钟" , 是软件开发中的一个重要组件 , 达到一个设定时间后就会执行某段代码.

例如网络通信中 , 如果对方500ms都没有返回数据 , 那么就执行定时器中的任务:断开重连.

例如实现一个Map , 希望 Map 中的 key 在3s后过期(自动删除).

...............................

类似于以上的场景就需要用到定时器.


2.标准库中的定时器

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

public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello");
            }
        },500);
    }

3.实现定时器

3.1定时期的构成:

  • 一个带优先级的阻塞队列 , 用来存放待执行的任务.

为什么要带优先级? 如果优先级队列按待执行时间(delay)来排序的话 , 每次只需取出队首的元素就可高效的把delay最小的任务给找出来.

  • 队列中每一个元素是 Task 对象.
  • Task 中带有一个时间属性 , 队首元素就是即将要执行的元素.
  • 同时有一个扫描线程一直扫描队首元素 , 查看队首元素是否到达执行时间.

3.2 实现步骤:

  • 1.Timer 类提供的核心方法是 schedule() , 用于注册一个任务并指定这个任务何时执行.
class MyTimer{
    public void schedule(Runnable runnable, long after){
        //具体执行的任务
    }
}
  • 2.Task 类用于描述一个任务 , 里面包含一个 Runnable 对象和一个 time毫秒时间戳. 该类的对象需要放入优先级队列中 , 因此需要实现 Comparable 接口.
class MyTask implements Comparable<MyTask>{
    private Runnable runnable;
    private long time;

    public MyTask(Runnable runnable, long time) {
        this.runnable = runnable;
        this.time = time;
    }
    //获取当前任务的时间
    public long getTime(){
        return time;
    }
    //执行任务
    public void run(){
        runnable.run();
    }

    @Override
    public int compareTo(MyTask1 o) {
        return (int) (this.time-o.time);
    }
}
  • 3.Timer 实例中 , 通过PriorityQueue来组织若干个 Task 对象 , 通过 schedule 往队列中插入一个个 Task 对象.
class MyTimer{
    PriorityBlockingQueue<MyTask1> queue = new PriorityBlockingQueue<>();
    public void schedule(Runnable runnable , long after){
        MyTask1 myTask = new MyTask(runnable , System.currentTimeMillis()+after);
        queue.put(myTask);
    }
}
  • 4.Timer 类中存在一个扫描线程 , 不断的查看队首元素是否到达执行时间.
class MyTimer1 {
    Thread scan = null;
    PriorityBlockingQueue<MyTask1> queue = new PriorityBlockingQueue<>();

    public void schedule(Runnable runnable, long after) {
        MyTask1 myTask1 = new MyTask1(runnable, System.currentTimeMillis() + after);
        queue.put(myTask1);
    }

    public MyTimer1() {
        scan = new Thread(() -> {
            while (true) {
                try {
                    MyTask1 myTask1 = queue.take();
                    if (System.currentTimeMillis() < myTask1.getTime()) {
                        //时间还没到把任务塞回去
                        queue.put(myTask1);
                    } else {
                        //时间到了执行任务
                        myTask1.run();
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        scan.start();
    }
}

但当前代码存在一个很严重的问题 , 就是while(true)循环速度太快 , 造成了无意义的 CPU 浪费.

  • 5. 借助 wait/notify 来解决 while(true) 问题 , 并且修改 MyTimer的 schedule方法 , 一但有新的任务加入就通知 wait 再次判断.
public void schedule(Runnable runnable, long after) {
        MyTask1 myTask1 = new MyTask1(runnable, System.currentTimeMillis() + after);
        queue.put(myTask1);
        //一但加入新的元素就唤醒 wait ,重新判断
        synchronized (this) {
            this.notify();
        }
    }
public MyTimer1() {
        scan = new Thread(() -> {
            while (true) {
                try {
                    MyTask1 myTask1 = queue.take();
                    if (System.currentTimeMillis() < myTask1.getTime()) {
                        //时间还没到把任务塞回去
                        queue.put(myTask1);
                        synchronized (this) {
                            this.wait(myTask1.getTime()-System.currentTimeMillis());
                        }
                    } else {
                        //时间到了执行任务
                        myTask1.run();
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        scan.start();
    }

此时代码还存在一点瑕疵 , 假设当前时间是 13:00 如果队首的任务执行时间是 14:00  , 这时当代码执行到 queue.put(myTask1);时该线程被 CPU 调度走 , 与此同时另一个线程调用 schedule 方法 , 注册一个 13:30 执行的任务 , 将其放入队首并通知 wait.但此时 notify 方法空打一炮 , 等到扫描线程被调度回来时 , wait 还是要等待1h , 这样机会导致 13:30 的任务错过其执行时间.

产生上述问题的根本原因是 , take() 和 wait 操作不是原子的 , 如果在 take() 和 wait 之间加上锁 , 保证这个执行过程中不会有新的任务进来 , 问题自然解决.

public MyTimer1() {
        scan = new Thread(() -> {
            while (true) {
                synchronized (this) {
                    try {
                        MyTask1 myTask1 = queue.take();
                        if (System.currentTimeMillis() < myTask1.getTime()) {
                            //时间还没到把任务塞回去
                            queue.put(myTask1);
                            this.wait(myTask1.getTime()-System.currentTimeMillis());
                        } else {
                            //时间到了执行任务
                            myTask1.run();
                        }
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        });
        scan.start();
    }

完整代码如下:

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

    public MyTask1(Runnable runnable, long time) {
        this.runnable = runnable;
        this.time = time;
    }
    //获取当前任务的时间
    public long getTime(){
        return time;
    }
    //执行任务
    public void run(){
        runnable.run();
    }

    @Override
    public int compareTo(MyTask1 o) {
        return (int) (this.time-o.time);
    }
}
/**
 * 定时器类
 */
class MyTimer1 {
    Thread scan = null;
    PriorityBlockingQueue<MyTask1> queue = new PriorityBlockingQueue<>();

    public void schedule(Runnable runnable, long after) {
        MyTask1 myTask1 = new MyTask1(runnable, System.currentTimeMillis() + after);
        queue.put(myTask1);
        //一但加入新的元素就唤醒 wait ,重新判断
        synchronized (this) {
            this.notify();
        }
    }

    public MyTimer1() {
        scan = new Thread(() -> {
            while (true) {
                synchronized (this) {
                    try {
                        MyTask1 myTask1 = queue.take();
                        if (System.currentTimeMillis() < myTask1.getTime()) {
                            //时间还没到把任务塞回去
                            queue.put(myTask1);
                                this.wait(myTask1.getTime()-System.currentTimeMillis());
                        } else {
                            //时间到了执行任务
                            myTask1.run();
                        }
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        });
        scan.start();
    }
}
public class ThreadDemo8 {
    public static void main(String[] args) {
        MyTimer1 myTimer1 = new MyTimer1();
        myTimer1.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello,1");
            }
        },300);
        myTimer1.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello,2");
            }
        },600);
    }
}

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

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

相关文章

团灭LeetCode跳跃游戏(相关话题:贪心,BFS)

目录 LeetCode55跳跃游戏 LeetCode45. 跳跃游戏 II LeetCode1306. 跳跃游戏 III LeetCode1345. 跳跃游戏 IV LeetCode55跳跃游戏 给定一个非负整数数组 nums &#xff0c;你最初位于数组的 第一个下标 。 数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否…

win32com操作word 第三集:Range精讲(一)

本课程《win32com操作word API精讲&项目实战》&#xff0c;本公众号以文字分享为主&#xff0c;B站与视频号则发布视频分享&#xff0c;ID均为&#xff1a;一灯编程 本集开始&#xff0c;将会深入Document接口。打开或创建一个文档都会产生一个Document对象&#xff0c;它代…

十大排序(Java版本)

排序分为比较排序和非比较排序两种&#xff0c;常见的排序为比较排序&#xff0c;共有七类&#xff1a;直接插入排序、希尔排序、选择排序、冒泡排序、堆排序、快速排序以及归并排序。另有三种非基于比较类的排序&#xff1a;计数排序、基数排序和桶排序。基于比较的排序直接插…

TreeMap和TreeSet的介绍

目录 1、认识 TreeMap 和 TreeSet 2、TreeMap 的主要成员变量 3、TreeMap 的主要构造方法 4、TreeMap 和 TreeSet 的元素必须可比较 5、TreeMap 和 TreeSet 关于 key 有序 6、TreeMap 和 TreeSet 的关系 7、总结 1、认识 TreeMap 和 TreeSet TreeMap 和 TreeSet 是Ja…

探索SpringMVC-组件之ViewResolver

前言 ViewResolver也就是视图解析器&#xff0c;他将是我们《探索SpringMVC》系列要介绍的最后一个常用的组件。其他组件&#xff1a;MultipartResolver、LocaleResolver、ThemeResolver、RequestToViewNameTranslator、FlashMapManager&#xff0c;相对简单&#xff0c;大家可…

一个想活得简单的程序猿的2022年终总结!

前言 今年的总结相比以往来说&#xff0c;可写的太少了&#xff0c;但看到我17年开始写的年终总结&#xff0c;已定下每年写下的承诺&#xff0c;因此即便可写的不多&#xff0c;但是还是写下吧&#xff0c;毕竟又过了一年&#xff0c;总有东西会留下&#xff01; 今年事件 疫…

【Linux杂篇】Windows远程登陆Linux、Linux静态IP配置

前言 如果要长期连接Linux环境&#xff0c;就需要给Linux配置一个静态IP&#xff0c;否则可能每次连接的IP都不一样而且还很麻烦。 除此之外&#xff0c;我们使用ssh远程登录的时候&#xff0c;每次都要输入密码&#xff0c;也很麻烦&#xff0c;所以建议配置ssh密钥&#xff…

执行 java -jar xxx.jar 的时候底层到底做了什么?

大家都知道我们常用的 SpringBoot 项目最终在线上运行的时候都是通过启动 java -jar xxx.jar 命令来运行的。那你有没有想过一个问题&#xff0c;那就是当我们执行 java -jar 命令后&#xff0c;到底底层做了什么就启动了我们的 SpringBoot 应用呢&#xff1f;或者说一个 Sprin…

Redis删除了大量数据后,为什么内存占用还是很高?

前言 上周刚来了个应届小师弟&#xff0c;组长说让我带着&#xff0c;周二问了我这样一个问题&#xff1a;师兄啊&#xff0c;我用top命令看了下服务器的内存占用情况&#xff0c;发现Redis内存占用严重&#xff0c;于是我就删除了大部分不用的keys&#xff0c;为什么内存占用…

文件操作【C语言】

目录 一、为什么使用文件 二、什么是文件 1、程序文件 2、数据文件 3、文件名 三、文件的打开和关闭 1、文件指针 2、文件的打开和关闭 四、文件的顺序读写 五、文件的随机读写 1、fseek 2、ftell 3、rewind 七、文件读取结束的判定 1、被错误使用的feof 1、文…

unocss原子化

文章目录1. 安装2. 配置3. Unocss预设3.1 presetUno3.2 presetAttributify3.3 presetIcons了解什么是UnoCSS请看&#xff1a;重新构想原子化CSS - 知乎 github地址&#xff1a;UnoCSS UnoCSS搜索引擎 1. 安装 npm i -D unocss2. 配置 vite.config.ts import { defineConf…

分享微信抽奖小程序制作步骤_微信抽奖小程序怎么开发

各位商家在节日期间做活动的时候&#xff0c;都希望用更少的费用去或者更好的宣传和推广的效果。比较常见的就是抽奖活动小程序。无须玩家下载&#xff0c;通过微信扫码或者指定入口就可以参与。方便&#xff0c;效果又好。那么,性价比高的抽奖活动小程序怎么做&#xff1f; 来…

LabVIEW使用VI脚本重新排列对象

LabVIEW使用VI脚本重新排列对象VI脚本可用来重新排列前面板和程序框图的对象。该教程以程序框图对象重新排列为例。按照下列步骤&#xff0c;使用VI脚本重新排列程序框图对象。创建VI前&#xff0c;需先了解VI脚本的基本内容。必须启用VI脚本&#xff0c;才能显示VI脚本选板&am…

solr-cloud集群

Zookeeper集群搭建完成&#xff0c;下面开始构建solr-cloud从复制四个tomcat实例开始将配置好的单机版solr复制到tomcat实例下修改tomcat端口号vim tomcat01/conf/server.xmlvim tomcat02 /conf/server.xml使用配置好的单机版solrhome关联solr和solrhomevim tomcat01/webapps/s…

数据库系统概念 | 第三章:SQL介绍

文章目录&#x1f4da;SQL语言概览&#x1f4da;SQL数据定义&#x1f407;基本数据类型&#x1f407;基本模式定义&#x1f955;create table&#x1f955;create domain&#x1f955;drop table&#x1f955;delete table&#x1f955;alter table&#x1f4da;SQL查询的基本结…

Transformer模型详解

1. 前言 transformer结构是google在2017年的Attention Is All You Need论文中提出&#xff0c;在NLP的多个任务上取得了非常好的效果&#xff0c;可以说目前NLP发展都离不开transformer。最大特点是抛弃了传统的CNN和RNN&#xff0c;整个网络结构完全是由Attention机制组成。 …

VESC操作入门——控制霍尔电机、无感电机和AS5047P

目录一、设备说明二、VESC4驱动霍尔电机2.1、硬件准备2.2、硬件连接2.3、打开软件2.4、连接2.5、校准电机2.6、主界面操作三、VESC4驱动无感电机3.1、硬件准备3.2、硬件连接3.3、打开软件3.4、校准电机四、VESC4驱动AS5047P4.1、软硬件修改4.2、硬件准备4.3、硬件连接4.4、校准…

Win32解决透明字体改变时重叠的问题,GetClientRect与GetWindowRect的使用

透明字体,改变时发生文本重叠,解决办法是刷新窗体局部区域,该区域是文本或者按钮等控件的区域 Win32 API中使用InvalidateRect函数使指定区域失效,意味着要刷新该区域,再用UpdateWindow函数强迫窗体立即刷新 RECT rc; ... InvalidateRect(hWnd,&rc,true); UpdateWind…

Python操作文件及其内容的常用方式

Python操作文件及其内容的常用方式 文章目录Python操作文件及其内容的常用方式1&#xff1a;修改文件名1.1&#xff1a;修改指定文件名1.2&#xff1a;修改目录下的所有文件的文件名2&#xff1a;读取文件2.1&#xff1a;读取文件内容2.1.1&#xff1a;按行读取2.1.2&#xff1…

[Arduino]环境安装与配置

最近着迷与Arduio&#xff0c;可以连接控制各种器件帮助人类降低负担&#xff0c;如室内外温度动态采集、声控灯、自动给绿植浇水等各种应用&#xff0c;感觉挺有意思&#xff1b;随着最近两年物联网的推广及“万物互联”的普及&#xff0c;个人觉得物联网还是有点花样的&#…