热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

[JavaEE]定时器

专栏简介:JavaEE从入门到进阶题目来源:leetcode,牛客,剑指offer.创作目标:记录学习JavaEE学习历程希望在提升自己的同时,帮助他人,,与大家一起共同进步,互相





专栏简介: 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{
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 queue &#61; new PriorityBlockingQueue<>();
public void schedule(Runnable runnable , long after){
MyTask1 myTask &#61; new MyTask(runnable , System.currentTimeMillis()&#43;after);
queue.put(myTask);
}
}

  • 4.Timer 类中存在一个扫描线程 , 不断的查看队首元素是否到达执行时间.

class MyTimer1 {
Thread scan &#61; null;
PriorityBlockingQueue queue &#61; new PriorityBlockingQueue<>();
public void schedule(Runnable runnable, long after) {
MyTask1 myTask1 &#61; new MyTask1(runnable, System.currentTimeMillis() &#43; after);
queue.put(myTask1);
}
public MyTimer1() {
scan &#61; new Thread(() -> {
while (true) {
try {
MyTask1 myTask1 &#61; queue.take();
if (System.currentTimeMillis() //时间还没到把任务塞回去
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 &#61; new MyTask1(runnable, System.currentTimeMillis() &#43; after);
queue.put(myTask1);
//一但加入新的元素就唤醒 wait ,重新判断
synchronized (this) {
this.notify();
}
}
public MyTimer1() {
scan &#61; new Thread(() -> {
while (true) {
try {
MyTask1 myTask1 &#61; queue.take();
if (System.currentTimeMillis() //时间还没到把任务塞回去
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 &#61; new Thread(() -> {
while (true) {
synchronized (this) {
try {
MyTask1 myTask1 &#61; queue.take();
if (System.currentTimeMillis() //时间还没到把任务塞回去
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{
private Runnable runnable;
private long time;
public MyTask1(Runnable runnable, long time) {
this.runnable &#61; runnable;
this.time &#61; time;
}
//获取当前任务的时间
public long getTime(){
return time;
}
//执行任务
public void run(){
runnable.run();
}
&#64;Override
public int compareTo(MyTask1 o) {
return (int) (this.time-o.time);
}
}
/**
* 定时器类
*/
class MyTimer1 {
Thread scan &#61; null;
PriorityBlockingQueue queue &#61; new PriorityBlockingQueue<>();
public void schedule(Runnable runnable, long after) {
MyTask1 myTask1 &#61; new MyTask1(runnable, System.currentTimeMillis() &#43; after);
queue.put(myTask1);
//一但加入新的元素就唤醒 wait ,重新判断
synchronized (this) {
this.notify();
}
}
public MyTimer1() {
scan &#61; new Thread(() -> {
while (true) {
synchronized (this) {
try {
MyTask1 myTask1 &#61; queue.take();
if (System.currentTimeMillis() //时间还没到把任务塞回去
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 &#61; new MyTimer1();
myTimer1.schedule(new Runnable() {
&#64;Override
public void run() {
System.out.println("hello,1");
}
},300);
myTimer1.schedule(new Runnable() {
&#64;Override
public void run() {
System.out.println("hello,2");
}
},600);
}
}



推荐阅读
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
  • 海马s5近光灯能否直接更换为H7?
    本文主要介绍了海马s5车型的近光灯是否可以直接更换为H7灯泡,并提供了完整的教程下载地址。此外,还详细讲解了DSP功能函数中的数据拷贝、数据填充和浮点数转换为定点数的相关内容。 ... [详细]
  • 本文介绍了在iOS开发中使用UITextField实现字符限制的方法,包括利用代理方法和使用BNTextField-Limit库的实现策略。通过这些方法,开发者可以方便地限制UITextField的字符个数和输入规则。 ... [详细]
  • Android系统源码分析Zygote和SystemServer启动过程详解
    本文详细解析了Android系统源码中Zygote和SystemServer的启动过程。首先介绍了系统framework层启动的内容,帮助理解四大组件的启动和管理过程。接着介绍了AMS、PMS等系统服务的作用和调用方式。然后详细分析了Zygote的启动过程,解释了Zygote在Android启动过程中的决定作用。最后通过时序图展示了整个过程。 ... [详细]
  • 重入锁(ReentrantLock)学习及实现原理
    本文介绍了重入锁(ReentrantLock)的学习及实现原理。在学习synchronized的基础上,重入锁提供了更多的灵活性和功能。文章详细介绍了重入锁的特性、使用方法和实现原理,并提供了类图和测试代码供读者参考。重入锁支持重入和公平与非公平两种实现方式,通过对比和分析,读者可以更好地理解和应用重入锁。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 本文介绍了一个Java猜拳小游戏的代码,通过使用Scanner类获取用户输入的拳的数字,并随机生成计算机的拳,然后判断胜负。该游戏可以选择剪刀、石头、布三种拳,通过比较两者的拳来决定胜负。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • Java值传递机制的说明及示例代码
    本文对Java值传递机制进行了详细说明,包括形参和实参的定义和传递方式,以及通过示例代码展示了交换值的方法。 ... [详细]
  • 本文讨论了在openwrt-17.01版本中,mt7628设备上初始化启动时eth0的mac地址总是随机生成的问题。每次随机生成的eth0的mac地址都会写到/sys/class/net/eth0/address目录下,而openwrt-17.01原版的SDK会根据随机生成的eth0的mac地址再生成eth0.1、eth0.2等,生成后的mac地址会保存在/etc/config/network下。 ... [详细]
  • Java 11相对于Java 8,OptaPlanner性能提升有多大?
    本文通过基准测试比较了Java 11和Java 8对OptaPlanner的性能提升。测试结果表明,在相同的硬件环境下,Java 11相对于Java 8在垃圾回收方面表现更好,从而提升了OptaPlanner的性能。 ... [详细]
author-avatar
有心人php
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有