【JavaEE】定时器
- 一、什么是定时器
- 二、标准库中的定时器
-
-
- [2.1 Timer类的核心方法schedule](#2.1 Timer类的核心方法schedule)
- [2.2 Timer类的构造方法](#2.2 Timer类的构造方法)
-
- 三、模拟实现定时器
-
-
- [3.1 创建一个类,表示一个任务](#3.1 创建一个类,表示一个任务)
- [3.2 创建一个集合类,用来管理任务](#3.2 创建一个集合类,用来管理任务)
- [3.3 创建schedule方法,将任务方到队列当中](#3.3 创建schedule方法,将任务方到队列当中)
- [3.4 创建线程,执行对列中的任务](#3.4 创建线程,执行对列中的任务)
- [3.5 代码优化](#3.5 代码优化)
-
- 四、扩展
博客结尾有此篇博客的全部代码!!!
一、什么是定时器
定时器(Timer)是一种用于在特定时间或按照一定时间间隔执行任务的工具。它的核心功能是允许程序在指定的时间点或周期性地执行代码,而不需要程序一直轮询等待(是一种通过周期性检查某个条件是否满足来等待事件发生的技术)。
通俗来讲:即在设定的时间时刻执行某事的设备(例如闹钟,在指定的时间响铃),Java中的定时器会在到达设定的时间后,执行指定的代码。
二、标准库中的定时器
2.1 Timer类的核心方法schedule
标准库中提供定时器的类是Timer类,Timer类的核心方法是schedule;
timer.schedule(TimerTask task,Date time);
timer.schedule(TimerTask task,long delay,long period);
timer.schedule(TimerTask task,Date firstTime,long period);
2.2 Timer类的构造方法

java
public Timer() {
this("Timer-" + serialNumber());
}
以 Timer- +序列号作为定时器的名字。
java
public Timer(boolean isDaemon) {
this("Timer-" + serialNumber(), isDaemon);
}
是否将该定时器作为守护线程执行。
java
public Timer(String name) {
this(name, false);
}
以name作为定时器的名字。
java
public Timer(String name, boolean isDaemon) {
var threadReaper = new ThreadReaper(queue, thread);//可能是用于管理线程的生命周期
this.cleanup = CleanerFactory.cleaner().register(this, threadReaper);//可能是用于创建和管理资源的清除器
thread.setName(name);
thread.setDaemon(isDaemon);
thread.start();
}
设置线程的名称和守护状态,并启动线程以执行定时任务。
三、模拟实现定时器
思路:
- 创建一个类,表示一个任务
- 创建一个集合类,用来管理任务
- 创建schedule方法,将任务方到队列当中
- 创建线程,执行对列中的任务
3.1 创建一个类,表示一个任务
java
class MyTimerTask implements Comparable<MyTimerTask> {
private Runnable runnable;
private long time;
//初始化TimerTask任务
public MyTimerTask(Runnable runnable, long time) {
this.runnable = runnable;
this.time = time;
}
public void run(){
runnable.run();
}
public long getTime() {
return time;
}
@Override
public int compareTo(MyTimerTask o) {
return (int) (this.time-o.time);
}
}
getTime()方法:获取任务执行的时间戳(和当前时间的时间戳进行比较)
compareTo()方法:创建优先级队列,需要将距离当前时间近的任务排在靠前位置。
3.2 创建一个集合类,用来管理任务
java
private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
3.3 创建schedule方法,将任务方到队列当中
java
//实现schedule方法,将任务放入队列当中
public void schedule(Runnable runnable, long time){
synchronized (lock){
queue.offer(new MyTimerTask(runnable, time));
}
}
3.4 创建线程,执行对列中的任务
java
//创建线程,执行对队列中的任务
public MyTimer(){
Thread t = new Thread(()->{
while(true){
synchronized (lock){
while(queue.isEmpty()){
continue;
}
MyTimerTask task=queue.peek();
//获取当前时间的时间戳
long curTime = System.currentTimeMillis();
if(task.getTime()>curTime){
continue;
}else{
task.run();
queue.poll();
}
}
}
});
t.start();
}
3.5 代码优化

如果当前任务未到执行时间时,代码会不断重复执行队列的取出和塞回操作,这种现象被称为"忙等"。为了更有效地利用CPU资源,我们需要使用阻塞式等待而不是忙等。
第一时间我们会想到用sleep() 来阻塞等待,但是这里由于任务执行的不确定性,我们不知道设置多长时间,设置短了没效果,设置长了,当唤醒的时候,第一个代码已经过了执行时间该怎么办?
这里就要用到刚学不就得wait()和notify()!!!
java
import java.util.PriorityQueue;
class MyTimerTask implements Comparable<MyTimerTask> {
private Runnable runnable;
private long time;
//初始化TimerTask任务
public MyTimerTask(Runnable runnable, long time) {
this.runnable = runnable;
this.time = time;
}
public void run() {
runnable.run();
}
public long getTime() {
return time;
}
@Override
public int compareTo(MyTimerTask o) {
return (int) (this.time - o.time);
}
}
class MyTimer {
Object lock = new Object();
//创建优先级队列,按时间顺序顺序执行
private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
//创建线程,执行对队列中的任务
public MyTimer() {
Thread t = new Thread(() -> {
while (true) {
try {
synchronized (lock) {
while (queue.isEmpty()) {
lock.wait();
}
MyTimerTask task = queue.peek();
//获取当前时间的时间戳
long curTime = System.currentTimeMillis();
if (task.getTime() > curTime) {
lock.wait(task.getTime() - curTime);
} else {
task.run();
queue.poll();
}
}
} catch(InterruptedException e){
throw new RuntimeException(e);
}
}
});
t.start();
}
//实现schedule方法,将任务放入队列当中
public void schedule (Runnable runnable,long time){
synchronized (lock) {
queue.offer(new MyTimerTask(runnable, time));
lock.notify();
}
}
}
主要优化了两段代码:
- schedule中的加入notify()来唤醒wait()方法
- 将"忙等"优化掉,更有效的利用资源
四、扩展
上述模拟实现定时器适合执行时间相差较大任务,如果在这个时间段有大量集中的任务需要上述代码实现的定时器处理,此时就适合用基于循环数组实现的定时器处理!(用二维链表实现的,将时间段划分为一小块一小块,分别将各个时间段的任务放入链表中)
定时器默认是单线程执行任务,如果一个任务执行时间过长,可能一起阻塞等待,或者所有任务共享同一个线程,可能会导致资源竞争问题。
那么这里我们就可以用我们刚学的线程池来帮助我们解决这个问题!
java
ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);