定时器相当于一个“闹钟”,在日常生活中,我们需要闹钟的辅佐,在代码中,也经常需要“闹钟”机制(网络通信中经常需设定一个超时时间)。
一.定时器的使用
在Java标准库中,也停供了定时器的实现。
Timer类
Timer timer=new Timer();
timer类提供了一个重要的方法schedule()
需填写两个参数,用于安排任务,以及设定定时时间。
使用代码:
public class Demo1 {public static void main(String[] args) {Timer timer=new Timer();timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("hello");}},3000);System.out.println("程序开始运行");}
}
定时器可以多个一起使用,安排任务的先后顺序取决于所设置的延迟时间
public class Demo1 {public static void main(String[] args) {Timer timer=new Timer();timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("hello3");}},3000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("hello2");}},2000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("hello1");}},1000);System.out.println("程序开始运行");}
}
因hello1延迟时间短,所以hello1先打印,其次是hello2,最后hello3
二.定时器的实现
对于定时器来说 ,有以下几个要点:
1.创建类,描述一个要执行的任务是啥(任务的内容,任务的时间)
class MyTimerTask{private long time;private Runnable runnable;public MyTimerTask(Runnable runnable,long delay){this.runnable=runnable;this.time=System.currentTimeMillis()+delay;}public long getTime(){return time;}public void run(){runnable.run();}
}
time表示程序应该执行的时候,等于当前时间戳+延迟等待的时间。
2.管理多个任务,通过一定的数据结构,把多个任务存起来
首先,考虑使用怎样的数据结构储存。
按照我们的设想,应该是执行时间短的先执行,执行时间晚的在后面依次排队。
在学习数据结构时学到过大根堆,小根堆的数据结构,与场景符合。
我们可以使用优先级队列,在队列中放入MyTimerTask类。
private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
但无法直接放入MyTimerTask类,需指定放入的方式(根据什么放入)
public int compareTo(MyTimerTask o) {return (int)(this.time-o.time);}
通过比较时间,将MyTimerTask类放入队列。
实现schedule方法,将任务放入队列。
public void schedule(Runnable runnable,long delay){MyTimerTask myTimerTask=new MyTimerTask(runnable,delay);queue.offer(myTimerTask);}
3.有专门的线程去执行这些任务
创建线程去取出,执行队列中的任务。
首先,应该判断队列是否为空
如果为空
则需阻塞等待任务被放入队列
如果不为空,则查看任务,判断当前的时间戳有没有超过任务的时间戳。
如果没有超过,那就将任务进行阻塞等待,并设定等待的时间(任务执行的时间-当前时间戳),并在添加任务进队列时唤醒此处的等待。
注:这么做可以让出cpu资源,若是中间有执行时间更短的任务插队进来,可以让出cpu进行调度
如果当前时间>=任务执行的时间,那么就执行任务,并在执行后取出任务
class MyTimer{private Object lock=new Object();PriorityQueue<MyTimerTask> queue=new PriorityQueue<>();public MyTimer(){ Thread t=new Thread(()->{while (true) {synchronized (lock) {while (queue.isEmpty()) {try {lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}MyTimerTask current=queue.peek();if(System.currentTimeMillis()>=current.getTime()){current.run();queue.poll();}else{try {lock.wait(current.getTime()-System.currentTimeMillis());} catch (InterruptedException e) {e.printStackTrace();}}}}});t.start();}
然后,再考虑线程安全,以上操作中,涉及写和执行的操作,应该加上锁运行。
完整代码如下:
import java.util.PriorityQueue;class MyTimer{private Object lock=new Object();PriorityQueue<MyTimerTask> queue=new PriorityQueue<>();public MyTimer(){ Thread t=new Thread(()->{while (true) {synchronized (lock) {while (queue.isEmpty()) {try {lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}MyTimerTask current=queue.peek();if(System.currentTimeMillis()>=current.getTime()){current.run();queue.poll();}else{try {lock.wait(current.getTime()-System.currentTimeMillis());} catch (InterruptedException e) {e.printStackTrace();}}}}});t.start();}public void schedule(Runnable runnable,long delay) {synchronized (lock) {MyTimerTask myTimerTask = new MyTimerTask(runnable, delay);queue.offer(myTimerTask);lock.notify();}}}class MyTimerTask implements Comparable<MyTimerTask>{private long time;private Runnable runnable;public MyTimerTask(Runnable runnable,long delay){this.runnable=runnable;this.time=System.currentTimeMillis()+delay;}public long getTime(){return time;}public void run(){runnable.run();}@Overridepublic int compareTo(MyTimerTask o) {return (int)(this.time-o.time);}
}
测试如下:
public static void main(String[] args) {MyTimer myTimer=new MyTimer();myTimer.schedule(()->{System.out.println("hello 3000");},3000);myTimer.schedule(()->{System.out.println("hello 2000");},2000);myTimer.schedule(()->{System.out.println("hello 1000");},1000);}
补充
为什么队列不使用PriorityBlockingQueue而是要自己加锁?
使用阻塞队列后,queue.take()操作可能会阻塞,wait()操作也可能会阻塞,此时就有了两把锁。
两把锁,多个线程,容易出现死锁的情况。
需精心控制加锁的顺序,代码编程的复杂的提高。
如果不使用阻塞队列,可以通篇使用一把锁
以上便是全部内容,如有不对,欢迎指正