protected RunnableScheduledFuture decorateTask(Runnable runnable, RunnableScheduledFuture task) {
return task;``}`
protected <V> RunnableScheduledFuture<V> decorateTask(Callable<V> callable, RunnableScheduledFuture<V> task) {
`return task;``}
通过源码可以看出 decorateTask 方法的实现比较简单,接收一个 Runnable 接口对象或者 Callable 接口对象和封装的 RunnableScheduledFuture 任务,两个方法都是将 RunnableScheduledFuture 任务直接返回。在 ScheduledThreadPoolExecutor 类的子类中可以重写这两个方法。
接下来,我们继续看下 scheduleAtFixedRate 方法。
scheduleAtFixedRate 方法
scheduleAtFixedRate 方法源代码如下所示。
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
//传入的Runnable对象和TimeUnit为空,则抛出空指针异常
if (command == null || unit == null)
throw new NullPointerException();
//如果执行周期period传入的数值小于或者等于0
//抛出非法参数异常
if (period <= 0)
throw new IllegalArgumentException();
//将Runnable对象封装成ScheduledFutureTask任务,
//并设置执行周期
ScheduledFutureTask<Void> sft =
new ScheduledFutureTask<Void>(command, null, triggerTime(initialDelay, unit), unit.toNanos(period));
//调用decorateTask方法,本质上还是直接返回ScheduledFutureTask对象
RunnableScheduledFuture<Void> t = decorateTask(command, sft);
//设置执行的任务
sft.outerTask = t;
//执行延时任务
delayedExecute(t);
//返回执行的任务
return t;``}
通过源码可以看出,scheduleAtFixedRate 方法将传递的 Runnable 对象封装成 ScheduledFutureTask 任务对象,并设置了执行周期,下一次的执行时间相对于上一次的执行时间来说,加上了 period 时长,时长的具体单位由 TimeUnit 决定。采用固定的频率来执行定时任务。
ScheduledThreadPoolExecutor 类中另一个定时调度任务的方法是 scheduleWithFixedDelay 方法,接下来,我们就一起看看 scheduleWithFixedDelay 方法。
scheduleWithFixedDelay 方法
scheduleWithFixedDelay 方法的源代码如下所示。
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) {
//传入的Runnable对象和TimeUnit为空,则抛出空指针异常
if (command == null || unit == null)
throw new NullPointerException();
//任务延时时长小于或者等于0,则抛出非法参数异常
if (delay <= 0)
throw new IllegalArgumentException();
//将Runnable对象封装成ScheduledFutureTask任务
//并设置固定的执行周期来执行任务
ScheduledFutureTask<Void> sft =
new ScheduledFutureTask<Void>(command, null,triggerTime(initialDelay, unit), unit.toNanos(-delay));
//调用decorateTask方法,本质上直接返回ScheduledFutureTask任务
RunnableScheduledFuture<Void> t = decorateTask(command, sft);
//设置执行的任务
sft.outerTask = t;
//执行延时任务
delayedExecute(t);
//返回任务
return t;``}
从 scheduleWithFixedDelay 方法的源代码,我们可以看出在将 Runnable 对象封装成 ScheduledFutureTask 时,设置了执行周期,但是此时设置的执行周期与 scheduleAtFixedRate 方法设置的执行周期不同。此时设置的执行周期规则为:下一次任务执行的时间是上一次任务完成的时间加上 delay 时长,时长单位由 TimeUnit 决定。也就是说,具体的执行时间不是固定的,但是执行的周期是固定的,整体采用的是相对固定的延迟来执行定时任务。
如果大家细心的话,会发现在 scheduleWithFixedDelay 方法中设置执行周期时,传递的 delay 值为负数,如下所示。
ScheduledFutureTask<Void> sft =
new ScheduledFutureTask<Void>(command, null, triggerTime(initialDelay, unit), unit.toNanos(-delay));
这里的负数表示的是相对固定的延迟。
在 ScheduledFutureTask 类中,存在一个 setNextRunTime 方法,这个方法会在 run 方法执行完任务后调用,这个方法更能体现 scheduleAtFixedRate 方法和 scheduleWithFixedDelay 方法的不同,setNextRunTime 方法的源码如下所示。
private void setNextRunTime() {
//距离下次执行任务的时长
long p = period;
//固定频率执行,
//上次执行任务的时间
//加上任务的执行周期
if (p > 0)
time += p;
//相对固定的延迟
//使用的是系统当前时间
//加上任务的执行周期
else
time = triggerTime(-p);``}
在 setNextRunTime 方法中通过对下次执行任务的时长进行判断来确定是固定频率执行还是相对固定的延迟。
triggerTime 方法
在 ScheduledThreadPoolExecutor 类中提供了两个 triggerTime 方法,用于获取下一次执行任务的具体时间。triggerTime 方法的源码如下所示。
private long triggerTime(long delay, TimeUnit unit) {
return triggerTime(unit.toNanos((delay < 0) ? 0 : delay));``}
long triggerTime(long delay) {
return now() +
((delay < (Long.MAX_VALUE >> 1)) ? delay : overflowFree(delay));``}
这两个 triggerTime 方法的代码比较简单,就是获取下一次执行任务的具体时间。有一点需要注意的是:delay < (Long.MAX_VALUE >> 1 判断 delay 的值是否小于 Long.MAX_VALUE 的一半,如果小于 Long.MAX_VALUE 值的一半,则直接返回 delay,否则需要处理溢出的情况。
我们看到在 triggerTime 方法中处理防止溢出的逻辑使用了 overflowFree 方法,接下来,我们就看看 overflowFree 方法的实现。
overflowFree 方法
overflowFree 方法的源代码如下所示。
private long overflowFree(long delay) {
//获取队列中的节点
Delayed head = (Delayed) super.getQueue().peek();
//获取的节点不为空,则进行后续处理
if (head != null) {
//从队列节点中获取延迟时间
long headDelay = head.getDelay(NANOSECONDS);
//如果从队列中获取的延迟时间小于0,并且传递的delay
//值减去从队列节点中获取延迟时间小于0
if (headDelay < 0 && (delay - headDelay < 0))
//将delay的值设置为Long.MAX_VALUE + headDelay
delay = Long.MAX_VALUE + headDelay;
}
//返回延迟时间
return delay;``}
通过对 overflowFree 方法的源码分析,可以看出 overflowFree 方法本质上就是为了限制队列中的所有节点的延迟时间在 Long.MAX_VALUE 值之内,防止在 ScheduledFutureTask 类中的 compareTo 方法中溢出。
ScheduledFutureTask 类中的 compareTo 方法的源码如下所示。
public int compareTo(Delayed other) {
if (other == this) // compare zero if same object
return 0;
if (other instanceof ScheduledFutureTask) {
ScheduledFutureTask<?> x = (ScheduledFutureTask<?>)other;
long diff = time - x.time;
if (diff < 0)
return -1;
else if (diff > 0)
return 1;
else if (sequenceNumber < x.sequenceNumber)
return -1;
else
return 1;
}
long diff = getDelay(NANOSECONDS) - other.getDelay(NANOSECONDS);
return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;``}
compareTo 方法的主要作用就是对各延迟任务进行排序,距离下次执行时间靠前的任务就排在前面。
delayedExecute 方法
delayedExecute 方法是 ScheduledThreadPoolExecutor 类中延迟执行任务的方法,源代码如下所示。
private void delayedExecute(RunnableScheduledFuture<?> task) {
//如果当前线程池已经关闭
//则执行线程池的拒绝策略
if (isShutdown())
reject(task);
//线程池没有关闭
else {
//将任务添加到阻塞队列中
super.getQueue().add(task);
//如果当前线程池是SHUTDOWN状态
//并且当前线程池状态下不能执行任务
//并且成功从阻塞队列中移除任务
if (isShutdown() &&
!canRunInCurrentRunState(task.isPeriodic()) &&
remove(task))
//取消任务的执行,但不会中断执行中的任务
task.cancel(false);
else
//调用ThreadPoolExecutor类中的ensurePrestart()方法
ensurePrestart();
}``}
可以看到在 delayedExecute 方法内部调用了 canRunInCurrentRunState 方法,canRunInCurrentRunState 方法的源码实现如下所示。
boolean canRunInCurrentRunState(boolean periodic) {
return isRunningOrShutdown(periodic ? continueExistingPeriodicTasksAfterShutdown : executeExistingDelayedTasksAfterShutdown);``}
可以看到 canRunInCurrentRunState 方法的逻辑比较简单,就是判断线程池当前状态下能够执行任务。
另外,在 delayedExecute 方法内部还调用了 ThreadPoolExecutor 类中的 ensurePrestart()方法,接下来,我们看下 ThreadPoolExecutor 类中的 ensurePrestart()方法的实现,如下所示。
void ensurePrestart() {
int wc = workerCountOf(ctl.get());
if (wc < corePoolSize)
addWorker(null, true);
else if (wc == 0)
addWorker(null, false);``}
在 ThreadPoolExecutor 类中的 ensurePrestart()方法中,首先获取当前线程池中线程的数量,如果线程数量小于 corePoolSize 则调用 addWorker 方法传递 null 和 true,如果线程数量为 0,则调用 addWorker 方法传递 null 和 false。
关于 addWork()方法的源码解析,大家可以参考【高并发专题】中的《 高并发之——通过ThreadPoolExecutor类的源码深度解析线程池执行任务的核心流程 》一文,这里,不再赘述。
reExecutePeriodic 方法
reExecutePeriodic 方法的源代码如下所示。
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!**
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!