前言
本人是一个刚刚上路的IT新兵,菜鸟!分享一点自己的见解,如果有错误的地方欢迎各位大佬莅临指导,如果这篇文章可以帮助到你,劳请大家点赞转发支持一下!
本篇文章为大家带来的仍然是多线程编程,计时器是许多场景都会应用到的一个非常方便快捷实用的类。
一、定时器是什么?
🦉定时器,顾名思义他的功能类似于一个闹钟,但又比闹钟更加智能便捷。
🎗️在手机上,你可以设置某个时间的闹钟,并备注上内容,以便提醒自己这个时间要干什么。
🧑🏻💻 编程里的定时器,也是如此,他需要你先设置"需要执行的代码",到了这个时间程序就会自动帮你执行这一段代码。
换句话说, 定时器就是属于你的超级免费劳动力,你给他布置什么时间去执行什么任务,时间到了他就会自动去执行对应的任务。
二、定时器如何使用
标准库提供了一个Timer类,这个类就是计时器。
而像里面添加闹钟的方法是schedule方法 ,这也是该类的核心方法。
1️⃣ 第一个参数:TimerTask类型的task,这个参数实际上与一个线程无异,所以要重写run方法
2️⃣ 第二个参数:long类型的delay,就是延迟执行的时间。意思就是多少ms后执行第一个参数这个线程。 单位:ms
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("3000ms后");
}
},3000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("2000ms后");
}
},2000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("1000ms后");
}
},1000);
}
🎪 可以看到,三个任务都执行完以后,程序并没有结束,是因为Timer类内部有一个前台线程,会阻止进程结束。
三、代码模拟实现定时器
1.理论准备
Timer内部有两个核心的成员:
1️⃣ TaskQueue类型的queue。
2️⃣ TimerThread类型的thread。
thread是一个Timer内置的前台线程,他会去按时执行定时器中的任务。
核心方法是 使用while循环让他按时去执行计时器中已有的任务与后续添加的任务 。
这也就是为什么上面,计时器中所有任务都执行完毕,进程仍没有结束的原因。
所以咱们自己写一个定时器, 也需要一个线程去按时执行任务,并且不确定后续会不会有新添加的任务,所以在线程里也要搞一个死循环 。
queue是用来存放线程任务的队列,而 定时器的优先级不是先来后到,而是基于时间快慢。
就比如:
你想一小时后吃早饭,定了一个🍙吃饭闹钟。
你又想半小时后起床,定了一个🛏️起床闹钟。
那么起床闹钟一定比吃饭闹钟先响!
所以存储任务的数据结构我们选择使用一个优先级队列。因为涉及到了线程,我们就 使用一个线程安全的带有优先级的阻塞队列PriorityBlockingQueue
。
上述两个内容都了解之后,我们还需要一个类来 存储任务 与 延迟的时间 。与schedule方法中的TimerTask类似 。
该类需要两个成员变量:
1️⃣一个Runnable类型来存放任务。
2️⃣一个long类型来确定执行时间,以便放入队列中。(六点的两小时后 ==
七点的一小时后。)存放该任务的时间+延迟执行的时间==该任务的执行时间
2.代码实现
任务类
class MyTask implements Comparable<MyTask>{
// 存储任务
public Runnable runnable;
// 延迟的时间
public long time;
public MyTask(Runnable runnable, long dely) {
this.runnable = runnable;
//获取当前时间戳并+dely,作为当前任务实际执行的时间戳
this.time = dely + System.currentTimeMillis();
}
// 重写compareTo方法,让先执行的任务在优先级阻塞队列的队首
@Override
public int compareTo(MyTask o) {
return (int)(this.time - o.time);
}
}
计时器
public class MyTimer {
// 创建优先级阻塞队列,计时器的核心数据结构
private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
public void schedule(Runnable runnable,long delay) {
// 创建任务
MyTask myTask = new MyTask(runnable,delay);
// 将任务放进计时器
queue.put(myTask);
}
public MyTimer() {
// 内置线程执行计时器中的任务,并等待添加任务
Thread thread = new Thread(() -> {
while (true) {
try {
// 取出计时器中最优先执行的任务
// 优先级阻塞队列,不用担心队列为空抛出异常
// 队列为空则会阻塞等待
MyTask myTask = queue.take();
// 判断是否到达执行时间
if(System.currentTimeMillis() >= myTask.time) {
// 到达执行
myTask.runnable.run();
} else {
// 未到达再放回计时器
queue.put(myTask);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
}
}
运行结果:
3.🧑🏻💻优化代码
上述的代码运行结果正确,没有什么问题了。
🧑🏻💻🧑🏻💻🧑🏻💻但是还可以进行优化。
忙等问题
如果这个队列里最先执行的任务也要一个小时后才执行,也就是说程序在这一个小时中就要一直重复 取出任务 , 判断时间 , 放回任务 这一系列操作,期间会一直占用CPU资源。
CPU的执行效率之高,执行速度之快,一秒钟可以执行上亿次语句,那么忙等问题就浪费了天文数字量级的CPU资源 。
💉💉💉那么如何解决这个忙等问题
呢。
很简单,只需要使用前面分享过的 wait方法 。
但是就会出现一个新的问题:
还是上述情景, 添加了wait方法后,应该是取出这个任务后,让当前线程等待一小时,这期间不再占用CPU资源 。
如果我又插入了一个十分钟后就需要执行的任务呢??
那么此时线程还在等待中,不会被CPU调度执行,那么这个十分钟后执行的任务就无法被及时执行。
💉💉💉因此每次插入一个新的任务都要用 notify方法 来唤醒线程。
优化后代码:
public class MyTimer {
// 锁对象
Object locker = new Object();
// 创建优先级阻塞队列,计时器的核心数据结构
private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
public void schedule(Runnable runnable,long delay) {
// 创建任务
MyTask myTask = new MyTask(runnable,delay);
// 将任务放进计时器
queue.put(myTask);
synchronized (locker) {
// 叫醒线程
locker.notify();
}
}
public MyTimer() {
// 内置线程重复执行计时器中的任务
Thread thread = new Thread(() -> {
while (true) {
try {
synchronized (locker) {
// 取出计时器中最优先执行的任务
MyTask myTask = queue.take();
// 判断是否到达执行时间
if (System.currentTimeMillis() >= myTask.time) {
// 到达执行
myTask.runnable.run();
} else {
// 未到达再放回计时器
queue.put(myTask);
// 让线程等待相应时间
locker.wait(myTask.time - System.currentTimeMillis());
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
}
}
总结
以上就是今天要分享的内容了,本文介绍了定时器是什么,原理,以及代码模拟实现,定时器也是多线程编程中常常用的工具,希望大家都能掌握。文章来源:https://www.toymoban.com/news/detail-764012.html
路漫漫不止修身,也养性。文章来源地址https://www.toymoban.com/news/detail-764012.html
到了这里,关于Java 多线程6——计时器Timer的使用 + 详细代码模拟实现 + 代码优化的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!