🍎1.定时器概述
🍏1.1 认识定时器
java 中的定时器,也可以叫做任务定时执行器,顾名思义,就是等到了指定的时间,就去做指定的事情,就像你每周六或周日都要去力扣参加周赛一样。
所以你如果想要使用定时器,你需要交代时间和对应的任务才行,java 标准也提供了带有定时器功能的类Timer
。
🍏1.2Timer 类的使用
在 java1.8 中,Timer 给出了四个构造方法,这些构造方法可以去指定线程的名字和是否将定时器内部的线程指定为守护线程。
好了,又出现了一个新概念,这个守护线程是什么鬼?其实在 java 中有两种线程,一种是用户线程,另外一种是守护线程。用户线程就是普通的线程,守护线程顾名思义就是守护用户线程的线程,可以说就是用户线程的保姆,守护线程与 JVM“共存亡”, 只要存在一个用户线程,程序中所有的守护线程都不会停止工作,直到最后一个用户线程执行完毕,守护线程才会停止工作。守护线程最典型的应用就是 GC (垃圾回收器),它就是一个非常称职的守护者。
🍊构造方法:
Timer 类构造时内部也会创建线程,如果不指定,定时器对象内部的线程(为了简化,就称为关联线程吧)的类型是用户线程,而不是守护线程。
🍊核心方法:
🍊使用演示:
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.PriorityBlockingQueue;
public class TimeProgram {
public static void main(String[] args) throws InterruptedException {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("执行延后2s执行的任务!");
}
}, 2000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("执行延后5s执行的任务!");
}
}, 5000);
//每秒输出一个mian
for (int i = 0; i < 5; i++) {
System.out.println("main");
Thread.sleep(1000);
}
}
}
复制代码
🍊运行结果:
TimerTask 类就是专门描述定时器任务的一个抽象类,它实现了 Runnable 接口。
public abstract class TimerTask implements Runnable //jdk源码
复制代码
下面我们简单实现一下定时器,我们就不用 TimerTask 了,我们直接使用 Runnable,因为 TimerTask 实现了 Runnable 接口,所以后面测试我们自己所写的schedule
方法时,也可以传入 TimerTask 类型的引用,既然是简单地实现,那就不实现连续执行的功能了。
🍎2.定时器的简单实现
首先,我们需要建造一个类,来描述定时器的任务,可以使用 Runnable 加上一个任务执行的时间戳就可以了。🍊具体清单:一个构造方法,用来指定任务和延迟执行时间。两个获取方法,用来给外部对象获取该对象的任务和执行时间。实现比较器,用于定时器任务对象的组织,毕竟,每次需要执行时间最早的任务,需要用到基于小根堆实现的优先队列,不,还需要考虑多线程的情况,还是使用优先级阻塞队列吧。
//我的任务
class MyTask implements Comparable<MyTask> {
//接收具体任务
private Runnable runnable;
//执行时的时间戳
private long time;
//构造方法
public MyTask(Runnable runnable, int delay) {
this.runnable = runnable;
this.time = System.currentTimeMillis() + delay;
}
//执行任务
public void run() {
this.runnable.run();
}
//获取执行时间
public long getTime() {
return this.time;
}
//实现comparable接口,方便创建优先级阻塞队列
@Override
public int compareTo(MyTask o) {
return (int) (this.time - o.time);
}
}
复制代码
接下来就要实现定时器类了,首先我们需要一个数据结构来组织定时器任务,并且每次都能将时间最早的任务找到并执行,那么这个数据结构非小根堆莫属了,也就是优先级队列,注意对自定义类使用优先级队列时,一定要实现比较器。
//每次执行任务时,需要优先执行时间在前的任务,即每次执行任务要选择时间戳最小的任务,在多线程情况中优先级阻塞队列是最佳选择
private static final PriorityBlockingQueue<MyTask> priorityBlockingQueue = new PriorityBlockingQueue<>();
复制代码
然后,需要一个方法将任务安排在优先级阻塞队列中,最后在构造定时器对象的时候从优先级阻塞队列中取任务并在指定的时间执行。
按照上图的逻辑,我们自己实现的定时器类需要有一个线程专门去执行任务,执行任务过程中可能会遇到执行时间还没有到的情况,那么线程必须得等待,线程等待的方法有两种,一种是wait
另一种是sleep
,这个案例我们推荐前者,因为sleep
方法不能中途唤醒,这个案例是有可能需要中途唤醒的,那就是有新任务插入时,需要重新去优先级阻塞队列拿任务重复上述操作,这个唤醒操作可以使用notify
方法实现,所以需要用到wait/notify
组合拳,既然需要使用wait/notify
那么就得有锁,所以我们可以使用一个专门的锁对象来加锁。
🍊实现代码:
//我的定时类 用来管理任务
class MyTimer {
//专门对锁对象
private final Object locker = new Object();
//每次执行任务时,需要优先执行时间在前的任务,即每次执行任务要选择时间戳最小的任务,在多线程情况中优先级阻塞队列是最佳选择
private static final PriorityBlockingQueue<MyTask> priorityBlockingQueue = new PriorityBlockingQueue<>();
//安排任务
public void schedule(Runnable runnable, int delay) {
//将任务放入小根堆中
MyTask task = new MyTask(runnable, delay);
priorityBlockingQueue.put(task);
//每次当新任务加载到阻塞队列时,需要中途唤醒线程,因为新进来的任务可能是最早需要执行的
synchronized (locker) {
locker.notify();
}
}
public MyTimer() {
Thread thread = new Thread(() -> {
while (true) {
try {
//加载任务,确定执行时机
MyTask myTask = priorityBlockingQueue.take();
long curTime = System.currentTimeMillis();
//时间未到,将任务放回
if (curTime < myTask.getTime()) {
synchronized (locker) {
priorityBlockingQueue.put(myTask);
//放回任务后,不能立即就再次取该任务加载,需要设置一个再次加载的等待时间,建议使用wait带参数的方法
//因为wait方法可以使用notify进行中途唤醒,而sleep不能中途唤醒
int delay = (int)(myTask.getTime() - curTime);
locker.wait(delay);
}
} else {
System.out.println(Thread.currentThread().getName() + "线程收到任务,正在执行中!");
myTask.run();
System.out.println(Thread.currentThread().getName() + "线程执行任务完毕,正在等待新任务!");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//不要忘了启动线程
thread.start();
}
}
复制代码
🍊上面是我们实现定时器的代码,我们来测试一下:
import java.util.TimerTask;
import java.util.concurrent.PriorityBlockingQueue;
public class TimeProgram {
public static void main(String[] args) throws InterruptedException {
MyTimer timer = new MyTimer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("执行延后2s执行的任务!");
}
}, 2000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("执行延后5s执行的任务!");
}
}, 5000);
//每秒输出一个mian
for (int i = 0; i < 5; i++) {
System.out.println("main");
Thread.sleep(1000);
}
}
}
复制代码
🍊执行结果:
好了,任务定时执行器你学会了吗?
下期预告:Java 多线程案例之线程池
评论