借鉴:https://blog.youkuaiyun.com/qq_62731133/article/details/124482438
一:Timer类和TimerTask实现定时任务原理
代码使用:
Timer timer=new Timer();
//提交任务,将任务加到优先队列中(即最小堆)
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"执行第一个任务"+new Date());
}
},3000,2000);//3秒后执行,隔2秒执行一次
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"执行第二个任务"+new Date());
}
},5000,3000);
Timer类的缺点:
1.Timer是单线程,处理多个任务按照顺序执行,存在延时与设置定时器的时间有出入
2.某个任务的异常并且未捕获,导致Timer线程结束,从而影响后续任务执行
源码分析:
Timer类的线程 schedule() 方法加任务到优先队列,另启的单个线程TimerThread循环判断优先队列TaskQueue里的数组TimerTask[] 队首元素中的时间戳,当前时间匹配则更新任务的下次执行时间并插入优先队列(最小堆),然后执行任务。
(补充:Timer类在new对象之后,构造方法里面开启了内容的单线程TimerThread,此线程自旋获取队列队首的task执行,增删改优先队列并重新构建最小堆是加锁操作的)
Timer类:
public class Timer {
private final TaskQueue queue = new TaskQueue();
private final TimerThread thread = new TimerThread(queue);
//任务队列类里面是一个TimerTask数组
class TaskQueue {
private TimerTask[] queue = new TimerTask[128];
......
}
//线程类
class TimerThread extends Thread {
private TaskQueue queue;
TimerThread(TaskQueue queue) {
this.queue = queue;
}
public void run() {
try {
//单个线程自旋即循环判断优先队列TimerTask数组中第一个元素的时间,当了时间即执行。循环里面执行任务之前先把任务的下次执行时间更新,构建最小堆,然后执行任务。
mainLoop();
} finally {
// Someone killed this Thread, behave as if Timer cancelled
synchronized (queue) {
newTasksMayBeScheduled = false;
queue.clear(); // Eliminate obsolete references
}
}
}
......
}
public void schedule(TimerTask task, long delay, long period) {
if (delay < 0)
throw new IllegalArgumentException("Negative delay.");
if (period <= 0)
throw new IllegalArgumentException("Non-positive period.");
sched(task, System.currentTimeMillis() + delay, -period);
}
private void sched(TimerTask task, long time, long period) {
if (time < 0)
throw new IllegalArgumentException("Illegal execution time.");
// Constrain value of period sufficiently to prevent numeric
// overflow while still being effectively infinitely large.
if (Math.abs(period) > (Long.MAX_VALUE >> 1))
period >>= 1;
synchronized (queue) {
if (!thread.newTasksMayBeScheduled)
throw new IllegalStateException("Timer already cancelled.");
synchronized (task.lock) {
if (task.state != TimerTask.VIRGIN)
throw new IllegalStateException(
"Task already scheduled or cancelled");
task.nextExecutionTime = time;
task.period = period;
task.state = TimerTask.SCHEDULED;
}
queue.add(task);
if (queue.getMin() == task)
queue.notify();
}
}
//用来改变另启的线程的属性newTasksMayBeScheduled =false来停止所有的定时任务。TimerTask类的cancel是给当前任务的属性做个标记取消当前任务
public void cancel() {
synchronized(queue) {
thread.newTasksMayBeScheduled = false;
queue.clear();
queue.notify(); // In case queue was already empty.
}
}
......
}
TimerTask类:任务类有下次执行的时间戳和时间间隔等
public abstract class TimerTask implements Runnable {
final Object lock = new Object();
int state = VIRGIN;
static final int VIRGIN = 0;
static final int SCHEDULED = 1;
static final int EXECUTED = 2;
static final int CANCELLED = 3;
long nextExecutionTime;//下次执行的时间戳
long period = 0; //时间间隔
protected TimerTask() {
}
public abstract void run();
......
}
二:Executors.newScheduledThreadPool(3)线程池定时任务原理
原理:
原理和Timer的差不多,也是一个优先队列(最小堆)存TimerTask,只不过线程池的方式是多个另启的线程循环判断优先队列(最小堆),加锁更新任务的下次执行时间并且重新构建最小堆,然后由成功加锁的线程执行任务,其他的线程继续循环判断。优化了Timer类实现定时任务的两个缺点
两种时间间隔:
scheduleAtFixedRate:固定的时间间隔,原理是执行任务之前更新当前任务对象TimerTask的下次执行时间,然后构建最小堆,然后执行任务
scheduleWithFixedDelay:任务执行完之后再间隔,原理是执行任务之后更新当前任务到最小堆
代码使用:
ScheduledExecutorService pool= Executors.newScheduledThreadPool(3);
//开启定时任务
pool.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"输出:A\t"+new Date());
//当其中一个任务处理时间过长时,也不会影响其他任务的进行
try {
Thread.sleep(10000);
} catch (Exception e) {
e.printStackTrace();
}
}
},3,2, TimeUnit.SECONDS);
pool.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"输出:B\t"+new Date());
//如果其中一个定时器出现bug崩掉,也不会导致所有定时器崩掉
System.out.println(10/0);
}
},3,2,TimeUnit.SECONDS);
任务执行中抛异常未捕获:会把当前任务从最小堆中移除。捕获的话就正常的定时任务执行