目录
一.计时器的功能
由此我们可以对计时器的功能进行分析:
代码的简单实现:
计时器:类似于闹钟,可以设定时间,当时间到了之后去执行某个逻辑
import java.util.Timer;
import java.util.TimerTask;
public class Test {
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask(){
@Override
public void run() {
System.out.println("3000");
}
},3000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("2000");
}
},2000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("1000");
}
},1000);
}
}
1s后打印 1000,2s后打印2000,3s后打印3000.除非程序员手动结束,否则程序不会结束.
一.计时器的功能
上述代码我们可以看到计时器的一些基本功能,我们new一个Timer之后就可以通过其shedule方法向Timer传入TimerTask对象,其为抽象类,而TimerTask实现了Runnable接口需要我们通过匿名内部类来重写run方法.
剩下还需要传入一个时间参数,单位是ms,此时Timer就会以程序启动时间为基准,在经过传入时间之后执行TimerTask中的run方法.
由此我们可以对计时器的功能进行分析:
1.需要一个存储任务的结构,而很明显我们执行任务是有优先级的,也就是时间参数短的先执行,时间参数长的后执行,所以我们需要一个优先级队列来容纳我们的TimerTask
2.Timer一定是需要自带一个线程的,我们在主线程随时进行任务的传入,而Timer还需要定时对任务进行执行,这两者是不能够冲突的,所以Timer自带一个线程,此线程的作用就是对优先级队列进行不断的扫描,如果有任务到达执行时间就需要执行.
3.Timer中内置了线程,而且是前台线程,会阻碍进程的结束. timer不知道代码是否会添加新的任务,所以代码始终不会结束.Timer.cancel 来主动结束Timer
代码的简单实现:
package Thread;
import java.util.Comparator;
import java.util.PriorityQueue;
//通过这个类描述一个任务
class MyTimerTask implements Comparable
//在什么时间点执行这个任务
//约定为一个ms级别的时间戳
//时间戳:以1970年1月1日0时0分0秒为基准以秒/毫秒/微秒计算时间戳
private long time;
private Runnable runnable;
public long getTime(){
return time;
}
//delay是一个相对的时间间隔
public MyTimerTask( Runnable runnable , long delay) {
this.time = delay + System.currentTimeMillis();
this.runnable = runnable;
}
public void run (){
runnable.run();
}
@Override
public int compareTo(MyTimerTask o) {
return (int)(this.time - o.time);
}
}
//通过这个类表示定时器
class MyTimer{
private PriorityQueue
//优先级队列需要先去指定比较规则. 实现Comparable Comparator接口
private Thread t = null;
private Object locker = new Object();
public void schedule(Runnable runnable , long time) {
synchronized (locker) {
MyTimerTask myTimerTask = new MyTimerTask(runnable, time);
queue.offer(myTimerTask);
locker.notify();
}
}
public void cancel(){
t.interrupt();
}
//构造方法,创建扫描线程来完成判定和执行操作
public MyTimer(){
t = new Thread(() -> {
//扫描线程就需要循环反复的扫描队首元素判断队首元素是否时间到了.
//如果时间没到,什么也不干
//如果时间到了,就取出元素.
while(!t.isInterrupted()) {
try {
synchronized (locker) {
while (queue.isEmpty()) {
locker.wait();
}
MyTimerTask task = queue.peek();
long currentTime = System.currentTimeMillis();
if (currentTime >= task.getTime()) {
//执行
queue.poll();
task.run();
} else {
//1.有新的任务出现wait会被唤醒,notify
//2.wait过程中没有新的任务,就按照原定时间计划执行
locker.wait(task.getTime() - currentTime);
}
//这里的代码执行很快.很可能导致其他线程抢不到锁,造成线程饿死.
}
}catch(InterruptedException e){
e.printStackTrace();
}
}
});
t.start();
}
}
public class ThreadDemo8 {
public static void main(String[] args) throws InterruptedException {
MyTimer myTimer = new MyTimer();
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("1000");
}
} , 3000);
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("3000");
}
} , 2000);
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("2000");
}
} , 1000);
System.out.println(" hello man");
Thread.sleep(5000);
myTimer.cancel();
}
}
此代码相较于编译器内置源码更加简单,我们将MyTimerTask作为一个既包含Runnable,又包含时间的类,此时我们就可以将Runnable内部类和time一起传入MyTimerTask,这样其在优先级队列中就更容易地去比较.
令MyTimerTask实现Comparable接口,此时就可以决定优先级队列依据哪个变量升序还是降序排列.
结果: