Java中定时器java.util.Timer的简单模拟
作者:会飞的喵喵
1.定时器
1.1 含义
在Java中,定时器(Tim
er)是一个工具类,用于安排任务(Task)在指定时间后执行或以指定的时间间隔重复执行。它可以用于执行定时任务、定时调度和时间延迟等操作。定时器(Timer)可以应用于许多场景,比如:
- 调度任务:当你需要按照预定时间执行任务时,可以使用定时器。例如,每天凌晨执行数据备份、定时生成报表、定时发送通知等。
- 超时处理:当你需要处理某个操作的超时情况时,可以使用定时器。例如,设置一个操作的超时时间,如果在规定时间内未完成,则执行相应的超时处理逻辑。
1.2 标准库中的定时器
Java中的定时器:java.util.Timer
,它的常用方法:
方法 | 描述 |
---|---|
schedule(TimerTask task, Date time) | 安排在指定时间执行任务 |
schedule(TimerTask task, long delay) | 安排在指定延迟时间后执行任务 |
schedule(TimerTask task, long delay, long period) | 安排在指定延迟时间后以指定的时间间隔重复执行任务 |
scheduleAtFixedRate(TimerTask task, Date firstTime, long period) | 安排在指定时间开始以固定的时间间隔重复执行任务 |
scheduleAtFixedRate(TimerTask task, long delay, long period) | 安排在指定延迟时间后以固定的时间间隔重复执行任务 |
cancel() | 取消定时器的所有任务 |
purge() | 从定时器的任务队列中删除所有已取消的任务 |
public class Main { public static void main(String[] args) { Timer timer = new Timer(); //调度指定的任务在指定的延迟时间(3000ms)后执行。 timer.schedule(new TimerTask() { //待执行的任务 @Override public void run() { System.out.println("hello"); } },3000); } }
也可以一次注册多个任务:
public class Main { public static void main(String[] args) { Timer timer = new Timer(); //在指定的延迟时间(1000ms)后执行。 timer.schedule(new TimerTask() { //待执行的任务 @Override public void run() { System.out.println("任务1"); } },1000); //在指定的延迟时间(2000ms)后执行。 timer.schedule(new TimerTask() { //待执行的任务 @Override public void run() { System.out.println("任务2"); } },2000); //在指定的延迟时间(3000ms)后执行。 timer.schedule(new TimerTask() { //待执行的任务 @Override public void run() { System.out.println("任务3"); } },3000); } }
2.简单模拟实现定时器
2.1 实现思路
1.使用一个数据结构来保存所有的任务,这些任务是根据时间的大小来进行先后执行的,所以这里使用优先级队列。由于这里是多线程的环境,所以这里采用PriorityBlockingQueue(优先级阻塞队列)
,时间越小优先级越高。
2.我们需要使用一个线程来扫描定时器里面的任务是否到达执行时间,由于我们采用的是优先级队列数据结构,所以只需扫描队首元素。如果队首还没到执行时间,那么后面的元素不可能到达执行时间。
3.任务用一个类MyTask
来表示,这里需要实现Comparable
接口,因为它需要存入优先级队列。其中的属性:
//表示定时器中的任务 class MyTask implements Comparable<MyTask>{ //要执行的任务内容 private Runnable runnable; //延迟时间 private long time; public MyTask(Runnable runnable, long time) { this.runnable = runnable; this.time = time; } //为了便于后面的比较,需要提供 get 方法 public long getTime() { return time; } //表示任务开始执行 public void run(){ this.runnable.run(); } @Override public int compareTo(MyTask o) { return (int)(this.getTime() - o.getTime()); } }
4.实现添加任务的方法schedule
:
public class MyTimer { //扫描线程 private Thread thread; //优先级队列(这里为阻塞队列) private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>(); /** * 这个方法是用来注册(添加)任务的 * @param runnable 表示待执行的任务 * @param after 表示多少时间过后执行任务 */ public void schedule(Runnable runnable,long after){ //添加任务,注意这里的时间是 System.currentTimeMillis() + after MyTask task = new MyTask(runnable,System.currentTimeMillis() + after); queue.put(task); } }
5.添加一个线程来检测队首元素:
//当创建对象的时候就直接开启一个线程 public MyTimer(){ thread = new Thread(()->{ while(true){ //取出队首,如果到时间了就执行。 try { MyTask myTask = queue.take(); long curTime = System.currentTimeMillis(); if(curTime < myTask.getTime()){ //时间未到,不执行 queue.put(myTask); }else { //时间已到,执行 myTask.run(); } } catch (InterruptedException e) { e.printStackTrace(); } } }); thread.start(); }
就这样就完了吗?其实不然,在上面代码中while (true)
转的太快了, 造成了无意义的 CPU 浪费,如果第一个任务设定的是 1 min 之后执行某个逻辑,那么在这一分钟内 CPU 会一直存取队首元素。所以这里需要借助该对象的wait / notify
来解决 while (true)
的忙等问题。
public MyTimer(){ thread = new Thread(()->{ while(true){ //取出队首,如果到时间了就执行。 try { MyTask myTask = queue.take(); long curTime = System.currentTimeMillis(); if(curTime < myTask.getTime()){ queue.put(myTask); //时间未到,不执行,这里的 this 表示 MyTimer 对象 synchronized (this){ //阻塞一段时间 this.wait(myTask.getTime() - curTime); } }else { //时间已到,执行 myTask.run(); } } catch (InterruptedException e) { e.printStackTrace(); } } }); thread.start(); } /** * 这个方法是用来注册(添加)任务的 * @param runnable 表示待执行的任务 * @param after 表示多少时间过后执行任务 */ public void schedule(Runnable runnable,long after){ //添加任务,注意这里的时间是 System.currentTimeMillis() + after MyTask task = new MyTask(runnable,System.currentTimeMillis() + after); queue.put(task); synchronized(this){ this.notify(); } }
修改 Timer
的 schedule
方法,每次有新任务到来的时候唤醒一下线程。(因为新插入的任务可能是需要马上执行的)。
还没结束!上面的代码还是有缺陷的。假设当 thread
线程执行完 queue.take()
过后,myTask.getTime() - curTime
的值为 1 个小时。这时 CPU 调度了其它线程(假设为 t2) 执行, t2 线程调用 schedule
方法,延时时间为 30 分钟,并调用 put
方法,随后再执行 notify
方法。然而这时 wait
方法还没有执行,notify
相当于失效了。这时CPU再调度 thread
线程执行,但是 myTask.getTime() - curTime
的值本应是 30 分钟(新添加了一个任务),但是实际上却是 1 个小时。 这是因为queue.take()
与wait
不是原子操作,所以才导致这个问题的发生,下面是改进后的代码。
public MyTimer(){ thread = new Thread(()->{ while(true){ //取出队首,如果到时间了就执行。 try { synchronized (this){ MyTask myTask = queue.take(); long curTime = System.currentTimeMillis(); if(curTime < myTask.getTime()){ queue.put(myTask); //时间未到,不执行 //阻塞一段时间 this.wait(myTask.getTime() - curTime); }else { //时间已到,执行 myTask.run(); } } } catch (InterruptedException e) { e.printStackTrace(); } } }); thread.start(); }
2.2 完整代码
//表示定时器中的任务 class MyTask implements Comparable<MyTask>{ //要执行的任务内容 private Runnable runnable; //延迟时间 private long time; public MyTask(Runnable runnable, long time) { this.runnable = runnable; this.time = time; } //为了便于后面的比较,需要提供 get 方法 public long getTime() { return time; } //表示任务开始执行 public void run(){ this.runnable.run(); } @Override public int compareTo(MyTask o) { return (int)(this.getTime() - o.getTime()); } } public class MyTimer { //扫描线程 private Thread thread; //优先级队列 private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>(); public MyTimer(){ thread = new Thread(()->{ while(true){ //取出队首,如果到时间了就执行。 try { synchronized (this){ MyTask myTask = queue.take(); long curTime = System.currentTimeMillis(); if(curTime < myTask.getTime()){ queue.put(myTask); //时间未到,不执行 //阻塞一段时间 this.wait(myTask.getTime() - curTime); }else { //时间已到,执行 myTask.run(); } } } catch (InterruptedException e) { e.printStackTrace(); } } }); thread.start(); } /** * 这个方法是用来注册(添加)任务的 * @param runnable 表示待执行的任务 * @param after 表示多少时间过后执行任务 */ public void schedule(Runnable runnable,long after){ //添加任务,注意这里的时间是 System.currentTimeMillis() + after MyTask task = new MyTask(runnable,System.currentTimeMillis() + after); queue.put(task); synchronized(this){ this.notify(); } } }
以上就是Java中定时器java.util.Timer的简单模拟的详细内容,更多关于Java定时器的资料请关注脚本之家其它相关文章!