目录
前言:
1.什么是定时器
2.标准库中的定时器及使用
3.实现定时器
结束语:
前言:
在上一节中小编给大家介绍了多线程中的两个设计模式,单例模式和阻塞式队列模式,在单例模式中又有两种实现方式一种是懒汉模式,一种是饿汉模式,在这两种模式中我们推荐大家使用的是懒汉模式,虽然饿汉模式是天然的线程安全的,但是与饿汉模式相比起来效率没有懒汉模式的高。在阻塞式队列中给大家重点提到了生产者和消费者模型,这个是我们以后会经常用到的一种模式,当时小编为了大家好理解给大家举了两个例子一个是包饺子,一个就是三峡大坝的削峰填谷,希望大家重点理解这两个例子。这节中小编将给大家讲解一下多线程中的定时器,讲解一下什么是定时器,定时器的使用以及手动实现一个定时器。
1.什么是定时器
定时器也是软件开发中的一个重要的组件,类似于一个“闹钟”,达到一个设定的时间之后,就执行某个指定好的代码。
比如:网络通信中,如果对方500ms内没有返回数据,则断开连接尝试重连,比如一个Map,希望里面的某个key在3s之后过期(自动删除),类似于这样的场景就需要用到定时器。
2.标准库中的定时器及使用
在标准库中提供了一个类:Timer类。
Timer timer = new Timer( );
Timer类的核心方法为schedule。
- schedule包含了两个参数,第一个参数指定即将要执行的任务代码,第二个参数指定多长时间之后执行(单位为毫秒)。
timer.schedule( new TimerTack( ) {
@Override
public void run() {
System.out.println("hello");
}
} , 3000 );
下面我们就在idea中来给大家具体演示一下:
代码展示:
package Time;
import java.util.Timer;
import java.util.TimerTask;
public class ThreadDemo1 {
public static void main(String[] args) {
//创建一个定时器
Timer timer = new Timer();
//让hello4、hello3、hello2、hello1在线程启动之后分别在4s、3s、2s、1s之后执行。
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello4");
}
},4000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello3");
}
},3000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello2");
}
},2000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello1");
}
},1000);
System.out.println("hello0");
}
}
结果展示:
3.实现定时器
要想实现一个定时器我们就需要先来了解一下定时器的构成。
定时器的构成:
- 是一个带优先级的阻塞队列。
- 队列中的每一个元素是一个Task对象。
- Task中带有一个时间属性,队首元素就是即将要执行的元素。
- 同时有一个worker线程一直扫描队首元素,看队首元素是否需要执行。
这里给大家解释一下为啥要带优先级呢?
因为阻塞式队列中的任务都有各自执行时刻(delay),最先执行的任务一定是delay最小的,使用优先级的队列就可以高效的把这个delay最小的任务找出来了。所以这里的核心数据结构是“堆”!!!之前学习数据结构中的PriorityQueue就是一个带优先级的阻塞式队列。
注:具体的操作步骤请详细看代码内的注释!!!
代码展示:
package Time;
import java.util.PriorityQueue;
class MyTask implements Comparable<MyTask>{
public Runnable runnable;
//为了方便后续的判定,使用绝对的时间戳
public long time;
public MyTask(Runnable runnable, long delay) {
this.runnable = runnable;
//取当前时刻的时间戳 + delay,作为该任务实际执行的时间戳。
this.time = System.currentTimeMillis() + delay;
}
//指定一下在后续的优先级队列中我们是要按照时间来进行比较大小
@Override
public int compareTo(MyTask o) {
//这样的写法意味着每次取出的是时间最小的元素
return (int) (this.time - o.time);
}
}
//自己实现一个类似于Timer类的MyTimer
class MyTimer{
//这个结构要求带有优先级的阻塞队列,核心数据结构就是“堆”。
//PriorityQueue<> ———— <>里面的元素需要我们手动的封装一下,创建一个MyTask类,表示两方面的信息。1.执行的任务是啥。2.任务啥时候执行。
private PriorityQueue<MyTask> queue = new PriorityQueue<>();
//创建一个锁对象
private Object locker = new Object();
//此处的delay是一个形如3000这样的数字(指多长时间后执行该任务)
public void schedule(Runnable runnable, long delay) {
//根据参数,构造MyTask,插入队列即可。
synchronized (locker) {
synchronized (locker) {
MyTask myTask = new MyTask(runnable, delay);
queue.offer(myTask);
locker.notify();
}
}
}
//在这里构造线程,负责执行具体的任务
public MyTimer() {
Thread t = new Thread(() -> {
while (true) {
try {
synchronized (locker) {
//阻塞队列,只有阻塞的入队列和阻塞的出队列,没有阻塞的查看队首元素。
while (queue.isEmpty()) {
locker.wait();
}
MyTask myTask = queue.peek();
long curTime = System.currentTimeMillis();
if (curTime >= myTask.time) {
//时间到了,可以执行任务了
queue.poll();
myTask.runnable.run();
} else {
//时间还没到
locker.wait(myTask.time - curTime);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//启动线程
t.start();
}
}
public class ThreadDemo2 {
public static void main(String[] args) {
//创建一个定时器对象
MyTimer myTimer = new MyTimer();
//模仿之前的使用方式使用
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("hello4");
}
}, 4000);
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("hello3");
}
}, 3000);
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("hello2");
}
}, 2000);
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("hello1");
}
}, 1000);
System.out.println("hello0");
}
}
结果展示:
可以看到上述代码的执行结果与标准库中定时器的效果一样。
结束语:
这节中小编带着大家一起了解了Java标准库中定时器的使用方式,并给大家实现了一下定时器。希望这节对大家学习JavaEE有一定的帮助,想要学习的同学记得关注小编和小编一起学习吧!如果文章中有任何错误也欢迎各位大佬及时为小编指点迷津(在此小编先谢过各位大佬啦!)