定时器是软件开发中的一个重要组件,类似于一个"闹钟"当达到一个设定的时间之后,就执行某个指定好的代码(任务)。
Timer
JAVA标准库中已经为我们实现了一个定时器,我们直接new就行了。
Timer timer = new Timer();
Timer类中最重要的一个方法就是schedule(),这个方法用于设置定时器待执行的任务和执行任务的时间。
public static void main(String[] args) { Timer timer = new Timer(); //在3秒后打印3000 timer.schedule(new TimerTask() { @Override public void run() { System.out.println("3000"); } }, 3000); //在2秒后打印2000 timer.schedule(new TimerTask() { @Override public void run() { System.out.println("2000"); } }, 2000); //在1秒后打印1000 timer.schedule(new TimerTask() { @Override public void run() { System.out.println("1000"); } }, 1000); System.out.println("OK");}
可以发现在代码执行完毕后程序并没有结束,这是因为虽然主线程结束了但是Timer类中的线程在阻止程序结束,它们还在等待新的任务进来被执行。
为了可以更好的理解定时器的原理,下面进行简单的模拟实现。
模拟实现
首先我们需要先创建一个MyTimerTask类,该类主要用来保存待执行的任务,和执行该任务的时间点。
class MyTimerTask { public Runnable runnable; //存储绝对时间,后期直接和当前时间比较大小就行 public long time; public MyTimerTask(Runnable runnable, long time){ this.runnable = runnable; this.time = time + System.currentTimeMillis(); }}
接着写我们的定时器类MyTimer。首先我们应该先选择一种数据结构来存储所有的MyTimerTask,这里我推荐使用优先级队列,也就是堆。因为如果使用数组或链表来存储就需要你不断地遍历该数组/链表,而使用优先级队列就可以只检查队首元素是否到执行时间。
public class MyTimer { private final PriorityQueue<MyTimerTask> priorityQueue = new PriorityQueue<>();}
因为我们使用了优先级队列所以我们需要让MyTimerTask类可以进行比较。
class MyTimerTask implements Comparable<MyTimerTask>{ …… @Override public int compareTo(MyTimerTask o) { return (int) (this.time-o.time); }}
接着我们需要实现一个schedule方法可以接收定时任务和时间,因为是多线程代码所以我们还应该加一个属性用来充当锁对象。
public class MyTimer { private final PriorityQueue<MyTimerTask> priorityQueue = new PriorityQueue<>(); private Object lock = new Object(); public void schedule(Runnable runnable, long time) { synchronized (lock) { priorityQueue.add(new MyTimerTask(runnable, time)); } }}
此时我们还需要一个自动检查队列的线程,而且该线程并不需要程序员来启动和创建,所以我们可以将它写在构造方法中。
public MyTimer() { Thread t = new Thread(()->{ while(true) { synchronized (lock) { if (priorityQueue.isEmpty()) { //堆为空 } MyTimerTask myTimerTask = priorityQueue.peek(); if (myTimerTask.time <= System.currentTimeMillis()) { //执行当前任务 myTimerTask.runnable.run(); //将当前任务移除 priorityQueue.poll(); }else { //时间没到 } }; } }); t.start();}
此处我们可以用阻塞队列的思想,当堆为空或者执行时间没到就使用wait()进行等待。有人添加任务时就使用notify()进行唤醒线程。
public class MyTimer { private final PriorityQueue<MyTimerTask> priorityQueue = new PriorityQueue<>(); private Object lock = new Object(); public void schedule(Runnable runnable, long time) { synchronized (lock) { priorityQueue.add(new MyTimerTask(runnable, time)); lock.notify(); } } public MyTimer() { Thread t = new Thread(()->{ while(true) { synchronized (lock) { if (priorityQueue.isEmpty()) { //如果堆为空就阻塞等待 try { lock.wait(); } catch (InterruptedException e) { throw new RuntimeException(e); } } MyTimerTask myTimerTask = priorityQueue.peek(); if (myTimerTask.time <= System.currentTimeMillis()) { //执行当前任务 myTimerTask.runnable.run(); priorityQueue.poll(); }else { //时间没到就阻塞等待 try { lock.wait(myTimerTask.time-System.currentTimeMillis()); } catch (InterruptedException e) { throw new RuntimeException(e); } } }; } }); t.start(); }}
简单测试
public static void main(String[] args) { MyTimer timer = new MyTimer(); //在3秒后打印3000 timer.schedule(()->{ System.out.println("3000"); }, 3000); //在2秒后打印2000 timer.schedule(()->{ System.out.println("2000"); }, 2000); //在1秒后打印1000 timer.schedule(()->{ System.out.println("1000"); }, 1000); System.out.println("OK");}
完整代码
import java.util.PriorityQueue;class MyTimerTask implements Comparable<MyTimerTask>{ public Runnable runnable; //存储绝对时间,后期直接和当前时间比较大小就行 public long time; public MyTimerTask(Runnable runnable, long time){ this.runnable = runnable; this.time = time + System.currentTimeMillis(); } @Override public int compareTo(MyTimerTask o) { return (int) (this.time-o.time); }}public class MyTimer { private final PriorityQueue<MyTimerTask> priorityQueue = new PriorityQueue<>(); private Object lock = new Object(); public void schedule(Runnable runnable, long time) { synchronized (lock) { priorityQueue.add(new MyTimerTask(runnable, time)); lock.notify(); } } public MyTimer() { Thread t = new Thread(()->{ while(true) { synchronized (lock) { if (priorityQueue.isEmpty()) { //如果堆为空就阻塞等待 try { lock.wait(); } catch (InterruptedException e) { throw new RuntimeException(e); } } MyTimerTask myTimerTask = priorityQueue.peek(); if (myTimerTask.time <= System.currentTimeMillis()) { //执行当前任务 myTimerTask.runnable.run(); priorityQueue.poll(); }else { //时间没到就阻塞等待 try { lock.wait(myTimerTask.time-System.currentTimeMillis()); } catch (InterruptedException e) { throw new RuntimeException(e); } } }; } }); t.start(); }}