概述
一 简单使用
环境:jdk8
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
ScheduledExecutorService executorService=new ScheduledThreadPoolExecutor(1);
executorService.scheduleWithFixedDelay(()->{
System.out.println(dtf.format(LocalDateTime.now()));
},1,3, TimeUnit.SECONDS);
有关spring中定时器使用,请点击https://blog.csdn.net/yegeg/article/details/121654509
从new ScheduledThreadPoolExecutor可以看出,我们这个定时器也和线程池有关系,接下来看ScheduledThreadPoolExecutor类
它扩展了ThreadPoolExecutor ,说明它也是一个线程池,也实现了ScheduledExecutorService ,这个接口定义了定时器所拥有的方法
public interface ScheduledExecutorService extends ExecutorService {
/**
* Creates and executes a one-shot action that becomes enabled
* after the given delay.
*
*/
public ScheduledFuture<?> schedule(Runnable command,
long delay, TimeUnit unit);
/**
* Creates and executes a ScheduledFuture that becomes enabled after the
* given delay.
*/
public <V> ScheduledFuture<V> schedule(Callable<V> callable,
long delay, TimeUnit unit);
/**
* Creates and executes a periodic action that becomes enabled first
* after the given initial delay, and subsequently with the given
* period; that is executions will commence after
* {@code initialDelay} then {@code initialDelay+period}, then
* {@code initialDelay + 2 * period}, and so on.
* If any execution of the task
* encounters an exception, subsequent executions are suppressed.
* Otherwise, the task will only terminate via cancellation or
* termination of the executor. If any execution of this task
* takes longer than its period, then subsequent executions
* may start late, but will not concurrently execute.
*/
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit);
/**
* Creates and executes a periodic action that becomes enabled first
* after the given initial delay, and subsequently with the
* given delay between the termination of one execution and the
* commencement of the next. If any execution of the task
* encounters an exception, subsequent executions are suppressed.
* Otherwise, the task will only terminate via cancellation or
* termination of the executor.
*/
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit);
}
我们常用的shedule、fixedRate、fixedDelay都在这里有提前申明
二 ScheduledExecutorService和线程池有什么关系
从ScheduledThreadPoolExecutor类继承,我们就知道 ScheduledExecutorService继承于ThreadPoolExecutor ,那么我们定时任务也是用线程池中规则来执行的,这篇文章,我们重点关注是ScheduledExecutorService,现在我们来看下ScheduledThreadPoolExecutor的scheduleWithFixedDelay源码
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
if (delay <= 0)
throw new IllegalArgumentException();
// ①
ScheduledFutureTask<Void> sft =
new ScheduledFutureTask<Void>(command,
null,
triggerTime(initialDelay, unit),
unit.toNanos(-delay));
RunnableScheduledFuture<Void> t = decorateTask(command, sft);// ②
sft.outerTask = t;
delayedExecute(t); // ③
return t;
}
这个函数主要做了三件事
① 把我们的执行任务、延时、执行周期包装成ScheduledFutureTask
sequenceNumber 任务在创建时候递增,如果两个任务执行时间相同,就可以根据该变量来判断哪个任务先执行
time 任务执行需要等待时间
period 执行周期,如果是正数表示fixedRate任务,如果是负数表示fixedDelay任务,0表示非重复任务
② 执行了decorateTask方法,主要作用是一个扩展点,允许用户修改和替换task
③ 通过delayedExecute把任务放到一个队列中,并且通过ensurePrestart方法去保证至少有一个线程开始执行,ensurePrestart是线程池的方法,它保证有线程能启动去执行任务,有关线程池的原理,将在线程池文章中分析
private void delayedExecute(RunnableScheduledFuture<?> task) {
if (isShutdown())
reject(task);
else {
super.getQueue().add(task);
if (isShutdown() &&
!canRunInCurrentRunState(task.isPeriodic()) &&
remove(task))
task.cancel(false);
else
ensurePrestart();
}
}
到这里,把任务封装成一个task 放到队列中,并且开启了线程来执行。
三 它是怎么实现周期性调用呢
通过上面,我们把任务放到队列,并且开启线程来执行,他是怎么执行呢,我们知道用Thread去启动一个任务的时候,这个任务可以实现Runnable接口,然后去执行他里面的run,我们上面说封装的ScheduledFutureTask任务,也是实现了Runnable接口,那么线程在执行的时候,也会去执行run,我们来看下ScheduledFutureTask中重写run方法
public void run() {
boolean periodic = isPeriodic();//①
if (!canRunInCurrentRunState(periodic))
cancel(false);
else if (!periodic)
ScheduledFutureTask.super.run();// ②
else if (ScheduledFutureTask.super.runAndReset()) { // ③
setNextRunTime();
reExecutePeriodic(outerTask);
}
}
①处就是判断是不是周期任务,如果ScheduledFutureTask的变量period不等于0 表示周期任务
② 如果是一次性任务就执行ScheduledFutureTask.super.run(),执行这个方法的时候会去执行FutrueTask中的run。里面调用了我们自己写的要执行的任务,把这里理解成去执行我们自己的任务。
③ 如果是周期任务 就执行ScheduledFutureTask.super.runAndReset()并且获取下次执行的时间,我们先来看下runAndReset是什么,它也是FutrueTask中的方法,执行了该方法后,它会重置为初始状态。
setNextRunTime()去设置我们任务下次执行的所需等待的时间,这里就体现了fixedRate和fixedDelay执行周期的差别。
reExecutePeriodic又把我们的任务重新放到队列中,启动一个线程去执行
void reExecutePeriodic(RunnableScheduledFuture<?> task) {
if (canRunInCurrentRunState(true)) {
super.getQueue().add(task);
if (!canRunInCurrentRunState(true) && remove(task))
task.cancel(false);
else
ensurePrestart();
}
}
这样就实现了我们任务周期性调用,放入队列-->调用任务-->计算下次执行时间-->放入队里-->调用任务-->计算下次执行时间....
四 怎么实现获取即将要执行的任务
从上面分析,我们知道了任务是怎么周期性的执行,我们也知道每个任务都有等待时间,那么我们是怎么获取即将要执行的等待时间最短的任务呢,等待这段时间线程做了什么?这里用到一个叫DelayedWorkQueue的队列,他是一个阻塞队列,线程池中的线程会通过他的taken来获取要执行的任务,taken方法会阻塞直到能获取最近要执行的任务才返回,任务是放到一个最小堆里面,堆顶就是等待时间最少的任务,如果任务等待时间还没有到达,线程就会被阻塞,释放cup
思考下
DelayedWorkQueue队列在新增和移除任务时候,是怎样实现最小堆存放的
调用taken时候,是怎么把线程阻塞起来,又怎么被唤醒
最后
以上就是寂寞秋天为你收集整理的定时器ScheduledExecutorService原理分析的全部内容,希望文章能够帮你解决定时器ScheduledExecutorService原理分析所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复