一.什么是定时器?
计时器(Timer)是一种能够在指定时间或按照指定时间间隔执行任务的机制。它解决了“在什么时候做事”的问题。
定时器是一种实际开发中非常常用的组件:
例如:
- 数据定期备份
- 日志文件清理
- 缓存定期刷新
- 定时发送报告
- 系统健康检查
- 定时同步数据
都会用到定时器
二.java标准库中的定时器
标准库中提供了一个 Timer 类. Timer 类的核心方法为 schedule .
schedule 包含两个参数. 第一个参数指定即将要执行的任务代码(TimerTask类), 第二个参数指定多长时间之后执行 (单位为毫秒).
public static void main(String[] args)
{
Timer timer = new Timer();
//下面给timer中注册的这个任务,不是在调用schedule的线程中执行的,而是通过Timer内部的线程来负责执行的
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello 3000");
}
},3000);//这段代码代表程序开始三秒之后开始打印出
System.out.println("程序开始执行");
}

我们发现程序并没有结束,为什么呢?
是因为Timer内部有自己的线程,为了保证随时可以处理新安排的任务,这个线程会持续执行,并且这个线程还是一个前台线程 通过jdk的源码我们可以看到 前台线程是可以改为后台线程的

上述修改为后台线程 下边是Timer的内部线程


一个定时器中可以有多个任务的,定时器会根据这些任务设置的时间来合理的分配执行顺序
import java.util.Timer;
import java.util.TimerTask;
//Timer定时器
public class Demo36 {
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello 3000");
}
}, 3000);
System.out.println("程序开始执行");
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello 2000");
}
}, 2000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello 1000");
}
}, 1000);
System.out.println("hello main");
}
}

可以看到虽然代码上要先打印hello 3000 但是hello 1000先执行这就是定时器会按照时间合理分配执行顺序
三.自己实现定时器
思路以及相关问题和解决方法



细节问题:
问题1:线程安全问题

问题二:阻塞线程(等待任务到时和任务队列为空时)用wait还是sleep

问题三:实现比较方法(小根堆)

以下是完成代码
import java.util.PriorityQueue;
import java.util.concurrent.Executors;
//任务类 (自己实现定时器)
class MyTimerTask implements Comparable<MyTimerTask> {
private Runnable task;
private long time;
public MyTimerTask(Runnable task, long time) {
this.task = task;
this.time = time;
}
public void run() {
task.run();
}
public long getTime() {
return time;
}
@Override
public int compareTo(MyTimerTask o) {
return (int) (this.time - o.time);
}
}
//自己实现定时器
class MyTimer {
private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
public Object locker = new Object();
public void schedule(Runnable task, long delay) {
synchronized (locker) {
//以入队列的时间为基准
MyTimerTask timerTask = new MyTimerTask(task, System.currentTimeMillis() + delay);
queue.offer(timerTask);
locker.notify();
}
}
public MyTimer() {
//创建一个线程,负责执行任务队列中的任务(定时器中仅有一个队列)
Thread t = new Thread(() -> {
while (true) {
try {
synchronized (locker) {
//取出队首元素
while (queue.isEmpty()) {//可以用while 多一次判断 防止notify后队列为空
locker.wait();//这里sleep不好,因为会阻塞线程,导致其他任务无法执行
}
MyTimerTask task = queue.peek();
if (System.currentTimeMillis() < task.getTime()) {
//当前任务时间,如果比系统时间大,说明任务执行的时机未到
locker.wait(task.getTime() - System.currentTimeMillis());
} else {
task.run();
queue.poll();
}
}
}catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
}
public class Demo {
public static void main(String[] args) {
//创建定时器
MyTimer timer = new MyTimer();
//定时执行任务
timer.schedule(() -> System.out.println("Hello, 3000!"), 3000);
//定时执行任务
timer.schedule(() -> System.out.println("Hello, 2000!"), 2000);
//定时执行任务
timer.schedule(() -> System.out.println("Hello, 1000!"), 1000);
}
}
优化:
上述是就一个扫描线程来执行多个任务,当任务量小的时候还好,任务量一多,效率就极其低下,此时就需要优化,这是我们就可以用到线程池,一个扫描线程把扫描到的任务添加到线程池中来执行提高效率
java中也有实现ScheduledExcutorService(接口)
ScheduledThreadPoolExcutor(实现类继承上述接口)
ScheduledExecutorService executor = Executors.newScheduledThreadPool(4);
executor.schedule(() -> System.out.println("Hello, 3000!"), 3, TimeUnit.SECONDS);
550



被折叠的 条评论
为什么被折叠?



