[JavaEE]定时器

article/2025/10/10 19:47:53


专栏简介: 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() {@Overridepublic 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();}@Overridepublic 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();}@Overridepublic 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() {@Overridepublic void run() {System.out.println("hello,1");}},300);myTimer1.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hello,2");}},600);}
}


http://chatgpt.dhexx.cn/article/xQB6oH8h.shtml

相关文章

定时器的作用

一、简介。 在很多时候&#xff0c;我们设计网页时&#xff0c;为了某种表现形式&#xff0c;会使用到定时器这一功能&#xff0c;如&#xff1a;为了保证用户有仔细阅读我们的用户条款&#xff0c;我们会给确认按钮设置只有条款被打开&#xff0c;并超过5秒才允许点击。 二、…

定时器简介

文章目录 一.定时器基本介绍A.CPU时序B.定时器的原理 二.定时/计数器的相关寄存器A.定时器工作方式寄存器&#xff08;TMOD&#xff09;B.控制寄存器&#xff08;TCON&#xff09; 三.定时器的四种工作方式图解 一.定时器基本介绍 A.CPU时序 振荡周期&#xff1a;CPU外部晶振…

定时器基本常识

1.概念解读 1.1定时器和计数器&#xff0c;电路一样 1.2定时或者计数的本质就是让单片机某个部件数数 1.3当定时器用的时候&#xff0c;靠内部震荡电路数数 1.4当计数器用的时候&#xff0c;书外面的信号&#xff0c;读取针脚的数据 2.定时器怎么定时 定时器的本质原理&a…

定时器详解

1. 什么是定时器&#xff08;timer&#xff09; 定时器实际上就是Soc当中的一个内部外设。 &#xff08;1&#xff09;定时器与计数器 定时器常与计数器扯到一起&#xff0c;计数器也是soc当中的一个内部外设&#xff0c;计数器顾名思义是用来计数的&#xff0c;就和我们的秒…

定时器(Timer)

一、定时器是什么&#xff1f; 定时器类似于我们生活中的闹钟&#xff0c;可以设定一个时间来提醒我们。 而定时器是指定一个时间去执行一个任务&#xff0c;让程序去代替人工准时操作。 标准库中的定时器: Timer 方法作用void schedule(TimerTask task, long delay)指定dela…

STM32-定时器详解

目录 前言 一、定时器基本介绍 1. STM32定时器 2. 通用定时器功能和特点 3. 计数器模式 4. 定时器工作原理 a.定时器框图 b.时钟产生器部分 c.时基单元 d.输入捕获通道 e.输出比较通道&#xff08;PWM&#xff09; 二、定时器中断应用 1.内部时钟选择 2.计数器模式 …

typedef和#define

typedef是c语言中一个重要的关键字其作用是为一种数据类型定义了一个新的名字这里的类型包括&#xff08;int&#xff0c;char,double 等)和自定义数据类型&#xff0c;通俗一点来说就是为一种数据类型起一个别名 举个例子&#xff1a; 定义一个整型变量a并将其初始化为666&a…

typedef和define的区别、typedef的具体用法

typedef最核心的用法&#xff1a;给数据类型取别名&#xff0c;这个别名既可以是此数据类型的替换&#xff0c;也是指向此数据类型的指针。 具体用法&#xff08;对普通数据类型取别名&#xff09;&#xff1a; 对结构体数据类型取别名&#xff1a; typedef与define的区别&…

#define与typedef的区别

目录 &#xff08;1&#xff09;原理不同 &#xff08;2&#xff09;功能不同 &#xff08;3&#xff09;作用域不同 &#xff08;4&#xff09;对指针的操作不同 &#xff08;5&#xff09;补充 a.指针常量 b.常量指针 typedef和define都是替一个对象取一个别名&#x…

C语言中的typedef

C语言中的"typedef" 一、什么是typedef typedef是用于定义新的类型名&#xff0c;在编程中可以用typedef来定义新的类型名来代替已有的类型名 格式&#xff1a; typedef 已有类型名 新的类型名 通俗点说&#xff0c;就是为已有的类型取别名&#xff0c;例如 老鼠&am…

define 与typedef的区别

define 与typedef大体功能都是使用时给一个对象取一个别名&#xff0c;增强程序的可读性&#xff0c;但它们在使用时有以下几点区别&#xff1a; 1.定义不一样 define定义后面不用加分号&#xff0c;并且它的别名在对象的前面 typedef需要加分号&#xff0c;并且它的别后面替…

C语言学习笔记---typedef 简介

在单片机和操作系统中 typedef 会经常用到&#xff0c;它可以为某一个类型自定义名称。和#define比较类似。但是又有不同的地方。 typedef 创建的符号只能用于数据类型&#xff0c;不能用于值。而#define 创建的符号可以用于值。typedef 是由编译器来解释&#xff0c;而不是预…

typedef介绍

[20210330更新]&#xff1a;这篇博客写的时间有点久了:)。本次更新修改了博客内容中的错误和表述不当的地方。 本文介绍C语言中的关键字 typedef 的用法。 1 概述 typedef 为C语言的关键字&#xff0c;作用是为一种数据类型定义一个新名字&#xff0c;这里的数据类型包括内部…

【C语言】typedef的使用

目录 一、什么是typedef 二、typedef用法 1、对于数据类型使用例如&#xff1a; 2、对于指针的使用例如 3、对于结构体的使用 三、进阶typedef 1、数组指针 2、指针函数 3、Int *(*array[3])(int); 4、Void (*funA(int,void(*funB)(int)))(int); 四、Typedef与defin…

ubuntu etc 设置权限777带来的问题

ubuntu etc 设置权限777带来的问题--sudoers权限错误 在一次工作中&#xff0c;在ubutu通过命令行输入 sudo chmod -R 777 /etc 命令&#xff0c;误将etc目录权限更改为了777&#xff08;rwxrwxrwx&#xff09; 导致服务器无法ssh远程连接&#xff0c;而且sudo命令无法使用 提…

Linux修改文件权限为777

将文件权限改为777的命令为chmod 777 文件名 777说明&#xff1a; 你可以在linux终端先输入ll,可以看到如: -rwx-r--r-- (一共10个参数) 第一个跟参数跟chmod无关,先不管. 2-4参数:属于user 5-7参数:属于group 8-10参数:属于others 接下来就简单了:r>可读 w>可写 x&…

文件权限777

一个文件的权限为777(linux中 ls -l xxx.xxx &#xff08;xxx.xxx是文件名,查看的是xxx文件之中的文件权限)) 这三个数字分别表示&#xff1a;不同用户或用户组的权限。 第一个数字表示文件所有者的权限 第二个数字 表示与文件所有者同属一个用户组的其他用户的权限 第三个数…

Linux-权限管理(你听过777、755、644吗)

文章目录 组rwx权限修改权限-chmod修改文件所有者-chown修改文件所在组-chgrp 组 linux 中每个文件有所有者、所在组、其它组的概念。 类似linux 中的每个用户必须属于一个组&#xff0c;不能独立于组外&#xff0c;组的相关操作可参考&#xff1a;Linux-用户管理 所有者 文件…

Linux文件属性的777权限

一、文件属性概述 Linux系统是一种典型的多用户系统&#xff0c;不同的用户处于不同的地位&#xff0c;拥有不同的权限。为了保护系统的安全性&#xff0c;Linux系统对不同的用户访问同一文件&#xff08;包括目录文件&#xff09;的权限做了不同的规定。在Linux中我们可以使用…

Linux的777权限

Linux的每个文件一般都有三个权限 r–读&#xff0c;w–写&#xff0c;x–执行&#xff0c;其分别对应的数值为4&#xff0c;2&#xff0c;1。 输入ll可以查看到文件的权限。 聪明的同学已经看出来了——7即代表有读、写和执行的权限。那么为什么是三个7呢&#xff1f;“三”是…