当前位置: 首页 > news >正文

【Java笔记】定时器

目录

  • 1. 标准库中的定时器
  • 2. 自定义实现定时器
  • 3. 代码中存在的问题
    • 3.1 忙等
    • 3.2 线程调度问题
    • 3.3 同时添加时间为0的任务(难点)
    • 完整代码

1. 标准库中的定时器

标准库中提供了⼀个 Timer 类,Timer 类的核心方法为 schedule;
schedule 包含两个参数,第⼀个参数指定即将要执⾏的任务代码,第二个参数指定多长时间之后执行 (单位为毫秒);

public class Demo_801 {public static void main(String[] args) {// JDK中的类,创建一个定时器Timer timer = new Timer();// 向定时器中添加任务timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("任务1");}}, 1000); // 1秒后执行任务1timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("任务2");}}, 2000); // 2秒后执行任务2timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("任务3");}}, 3000); // 3秒后执行任务3}
}

执行结果:
在这里插入图片描述
执行完已有任务之后,就阻塞等待新的任务。

2. 自定义实现定时器

步骤:

  1. 用一个类来描述任务执行任务的时间
    具体任务的逻辑用Runable表示,执行时间的可以用一个long型delay去表示
    在这里插入图片描述
  2. 组织任务和时间对应的对象
    使用阻塞队列
    在这里插入图片描述
    因此使用优先级阻塞队列

在这里插入图片描述

  1. 提供一个方法提交任务

在这里插入图片描述

  1. 定义一个线程执行任务
    将线程定义在构造方法中
    在这里插入图片描述

当前代码:

/*** 自定义定时器*/
public class MyTimer {// 用一个阻塞队列来组织任务private BlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();public MyTimer() {// 创建扫描线程Thread thread = new Thread(() -> {while (true) {try {// 1. 从队列中取出任务MyTask take = queue.take();// 2. 判断任务是否到达执行时间long currentTime = System.currentTimeMillis();if (currentTime >= take.getTime()) {// 如果时间到了则执行任务take.getRunnable().run();} else {// 如果时间没到则把任务再放回到队列中queue.put(take);}} catch (InterruptedException e) {e.printStackTrace();}}});// 启动线程thread.start();}/*** 添加任务的方法*/public void schedule (Runnable runnable, long delay) throws InterruptedException {// 构造MyTaskMyTask myTask = new MyTask(runnable, delay);// 把任务放进阻塞队列中queue.put(myTask);}
}// 1. 用一个类来描述任务和执行任务的时间
class MyTask implements Comparable<MyTask> {// 任务private Runnable runnable;// 任务执行的时间private long time;public MyTask(Runnable runnable, long delay) {// 校验任务不能为空if (runnable == null) {throw new IllegalArgumentException("任务不能为空.");}// 时间不能为负数if (delay < 0) {throw new IllegalArgumentException("执行时间不能小于0.");}this.runnable = runnable;// 计算出任务执行的具体时间this.time = delay + System.currentTimeMillis();}public Runnable getRunnable() {return runnable;}public long getTime() {return time;}@Overridepublic int compareTo(MyTask o) {if (this.getTime() > o.getTime()) {return 1;} else if (this.getTime() < o.getTime()) {return -1;} else {return 0;}}
}

调用代码:

public class Demo_802 {public static void main(String[] args) throws InterruptedException {// 创建定时器对象MyTimer timer = new MyTimer();// 向定时器中添加任务timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("任务1");}}, 1000);timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("任务2");}}, 2000);timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("任务3");}}, 3000);}
}

执行结果:
在这里插入图片描述
目前看来代码执行结果是正确的,但是代码中依然存在一些比较严重的问题.

3. 代码中存在的问题

3.1 忙等

在这里插入图片描述
如图所示,如果当前时间是6点,执行时间最早的任务为7点,若按照当前代码的逻辑,线程会不停的从队列中取出任务,检查执行时间,然后放回阻塞队列,但是这都是无用功,而且while(true)会一直消耗系统资源,看起来很忙但都是无意义的,这就是 “忙等”
在这里插入图片描述

解决办法:通过 wait(time) 的方式让程序等待一段时间,等待时间就是当前时间与下一个执行任务的时间的差。

在这里插入图片描述
在添加任务时唤醒一下线程,重新计算等待时间

在这里插入图片描述

也就解决了忙等问题,但是还有其他问题。

3.2 线程调度问题

当线程执行到一半被CPU调度走:
在这里插入图片描述
在这里插入图片描述

由于线程调度的问题,t2先入队了新任务,执行时间在t1读取的任务执行时间之前,t1读的任务发现时间没有到放回队列的时候,设置的等待时间超过了新任务的执行时间,导致t2放入队列的新任务不能及时的执行。

造成这个现象的原因是没有保证原子性!
解决办法:加大锁的粒度
在这里插入图片描述
此时保证了原子性,不会出现中途被CPU调度走的问题。
但此时又引出了新的问题。

3.3 同时添加时间为0的任务(难点)

在这里插入图片描述
当同时添加0秒后执行的任务,打印结果为:
在这里插入图片描述
可见,定时器只执行了一个任务之后就阻塞等待了。

原因:

  1. 首先创建了一个定时器对象;

  2. 向定时器中添加了第一个任务,因为new对象是在JVM层面的,当启动线程时,阻塞队列中已经添加了一个任务;

  3. 扫描线程启动,处理第一个任务,打印出“马上执行任务1”;

  4. 扫描线程立即循环,获取第二个任务时,发现阻塞队列此时是空的(由于线程调度的不确定性,主线程尚未提交后续任务,扫描线程就已进入下一轮等待),开始阻塞等待,同时扫描线程获取到了锁对象;
    在这里插入图片描述

  5. 主线程向队列中添加任务的时候,等待扫描线程的锁对象,由于扫描线程无法释放锁对象,主线程也就获取不到锁对象,形成锁被长时间持有导致的阻塞;
    在这里插入图片描述

不是因为时间设置为0才会阻塞,是因为添加任务的执行时间间隔过短!

流程图:
在这里插入图片描述
解决办法:
在处理任务无法及时执行的问题时,扩大了加锁的范围,却又引入了更大的问题一般我们两害相全取其轻;
因此还是将锁的粒度缩小,因为造成长时间死锁问题比无法及时执行任务严重的多;
但为了解决无法及时执行任务的问题,可以创建一个后台的扫描线程,只做定时唤醒操作定时1秒或10ms,唤醒一次;
后台线程不会影响前台线程,不随着主线程的退出而退出。

public MyTimer() {// 创建扫描线程Thread thread = new Thread(() -> {while (true) {try {// 1. 从队列中取出任务MyTask take = queue.take();// 2. 判断任务是否到达执行时间long currentTime = System.currentTimeMillis();if (currentTime >= take.getTime()) {// 如果时间到了则执行任务take.getRunnable().run();} else {// 当前时间与任务执行时间的差long waitTime = take.getTime() - currentTime;// 如果时间没到则把任务再放回到队列中queue.put(take);synchronized (locker) {   // 减小锁粒度!!!!!!!!!!!// 等待时间locker.wait(waitTime);}}} catch (InterruptedException e) {e.printStackTrace();}}});// 启动线程thread.start();// 创建一个后台线程Thread deamonThread = new Thread(() -> {while (true) {synchronized (locker) {locker.notifyAll();}// 休眠一会儿try {TimeUnit.MICROSECONDS.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}}});// 设置为后台线程deamonThread.setDaemon(true);// 启动线程deamonThread.start();}

执行结果:
在这里插入图片描述
这种处理方式,首先可以保证正常业务的运行,又兼顾了小概率的事件

完整代码

MyTimer:

/*** 自定义定时器*/
public class MyTimer {// 用一个阻塞队列来组织任务private BlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();// 锁对象private Object locker = new Object();public MyTimer() {// 创建扫描线程Thread thread = new Thread(() -> {while (true) {try {// 1. 从队列中取出任务MyTask take = queue.take();// 2. 判断任务是否到达执行时间long currentTime = System.currentTimeMillis();if (currentTime >= take.getTime()) {// 如果时间到了则执行任务take.getRunnable().run();} else {// 当前时间与任务执行时间的差long waitTime = take.getTime() - currentTime;// 如果时间没到则把任务再放回到队列中queue.put(take);synchronized (locker) {// 等待时间locker.wait(waitTime);}}} catch (InterruptedException e) {e.printStackTrace();}}});// 启动线程thread.start();// 创建一个后台线程Thread deamonThread = new Thread(() -> {while (true) {synchronized (locker) {locker.notifyAll();}// 休眠一会儿try {TimeUnit.MICROSECONDS.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}}});// 设置为后台线程deamonThread.setDaemon(true);// 启动线程deamonThread.start();}/*** 添加任务的方法*/public void schedule(Runnable runnable, long delay) throws InterruptedException {// 构造MyTaskMyTask myTask = new MyTask(runnable, delay);// 把任务放进阻塞队列中queue.put(myTask);synchronized (locker) {// 唤醒等待的线程locker.notifyAll();}}
}// 1. 用一个类来描述任务和执行任务的时间
class MyTask implements Comparable<MyTask> {// 任务private Runnable runnable;// 任务执行的时间private long time;public MyTask(Runnable runnable, long delay) {// 校验任务不能为空if (runnable == null) {throw new IllegalArgumentException("任务不能为空.");}// 时间不能为负数if (delay < 0) {throw new IllegalArgumentException("执行时间不能小于0.");}this.runnable = runnable;// 计算出任务执行的具体时间this.time = delay + System.currentTimeMillis();}public Runnable getRunnable() {return runnable;}public long getTime() {return time;}@Overridepublic int compareTo(MyTask o) {if (this.getTime() > o.getTime()) {return 1;} else if (this.getTime() < o.getTime()) {return -1;} else {return 0;}}
}

Main方法:

public class Demo_802 {public static void main(String[] args) throws InterruptedException {// 创建定时器对象MyTimer timer = new MyTimer();// 向定时器中添加任务timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("马上执行任务1");}}, 0);// 向定时器中添加任务timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("马上执行任务2");}}, 0);// 向定时器中添加任务timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("马上执行任务3");}}, 0);// 向定时器中添加任务timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("任务1");}}, 1000);timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("任务2");}}, 2000);timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("任务3");}}, 3000);}
}
http://www.dtcms.com/a/548834.html

相关文章:

  • ih5做的网站怎么上传seo企业网站优化
  • 刚建设的网站多久能在百度查到最美情侣高清视频播放
  • 【JavaEE初阶】TCP核心机制10——异常情况的处理
  • 阿里云ECS在线扩容磁盘
  • 私人兼职做网站开发麻章手机网站建设
  • 如何把ChatGPT嵌入到自己的应用中?
  • 豆包、元宝、Kimi等AI对话大模型会成为“带货”主流吗?
  • 仓颉语言中流式I/O的设计模式深度剖析
  • 51单片机基础-IO扩展(并转串 74HC165)
  • 【LeetCode】49. 字母异位词分组
  • 创建网站需要注意的问题成都最专业做网站的
  • 上海松江网站制作南京市规划建设展览馆网站
  • 好用的大屏互动哪个公司好
  • Rust 深度指南:从 0 到 1,不只是学习,更是“思维重塑”
  • 题解:P2519 [HAOI2011] problem a
  • Rust async/await 语法糖的展开原理:从状态机到零成本异步
  • 车联网网络安全防护定级备案:数字时代交通变革下的安全基石
  • 李宏毅机器学习笔记36
  • Ubuntu(⑤Redis)
  • 【实战大全】MySQL连接全攻略:命令行+编程语言+可视化工具+故障排查
  • Python快速入门专业版(五十三):Python程序调试进阶:PyCharm调试工具(可视化断点与变量监控)
  • 企业建立网站需要什么条件wordpress divi
  • 如何解决笔记本电脑上不能使用管家婆软件快捷键的问题
  • MATLAB基于IOWHA算子和倒数灰关联度的组合预测模型
  • 从零搭建 Kafka + Debezium + PostgreSQL:打造实时 CDC 数据流系统
  • 酒吧网站设计网站建设及网络营销
  • 5分钟启动标准化安卓环境:Docker-Android让模拟器配置不再踩坑
  • VSCode + XMake搭建OpenGL开发环境
  • vscode ssh远程连接 ubuntu虚拟机
  • AIRSKIN®机器人电子皮肤传感器:为科研机器人披上智能“皮肤”