delayedExecute()
是延迟执行和周期性执行的主函数,其基本流程如下:
-
判断线程池的状态,
runstate
为shutdown
将拒绝任务提交。 -
任务处于正常运行状态,则将任务直接加入阻塞工作队列。
-
再次判断线程池的状态,
runstate
为shutdown
,再判断是否是周期性任务(isPeriodic
),不同的性质不同的处理策略。 -
一起正常预启动一个空
Worker
线程,循环从阻塞队列中消费任务。
private void delayedExecute(RunnableScheduledFuture<?> task) {
if (isShutdown())
reject(task);
else {
//1、直接加入延时阻塞队列
super.getQueue().add(task);
if (isShutdown() &&
!canRunInCurrentRunState(task.isPeriodic()) &&
remove(task))
task.cancel(false);
else
//2、预启动一个空的worker
ensurePrestart();
}
}
void ensurePrestart() {
int wc = workerCountOf(ctl.get());
if (wc < corePoolSize)
//创建一个空worker,并且启动
addWorker(null, true);
else if (wc == 0)
addWorker(null, false);
}
三、ScheduledFutureTask时间调度执行的核心
可以看出提交的任务最重被包装成ScheduledFutureTask
,然后加到工作队列由Worker
工作线程去消费了。
延迟执行和周期性执行的核心代码也就在于ScheduledFutureTask
。
1、基本架构
ScheduledFutureTask
继承了FutureTask
并实现了接口RunnableScheduledFuture
。
private class ScheduledFutureTask
extends FutureTask implements RunnableScheduledFuture {
/** Sequence number to break ties FIFO */
private final long sequenceNumber;
/** The time the task is enabled to execute in nanoTime units */
任务被调用的执行时间
private long time;
/**
- Period in nanoseconds for repeating tasks.
*/
//周期性执行的时间间隔
private final long period;
/** The actual task to be re-enqueued by reExecutePeriodic */
RunnableScheduledFuture outerTask = this;
/**
-
Index into delay queue, to support faster cancellation.
-
索引到延迟队列为了支持快速取消
*/
int heapIndex;
/**
- Creates a one-shot action with given nanoTime-based trigger time.
*/
ScheduledFutureTask(Runnable r, V result, long ns) {
super(r, result);
this.time = ns;
this.period = 0;
this.sequenceNumber = sequencer.getAndIncrement();
}
}
ScheduledFutureTask
实现了接口Delayed
,所以需要重写两个方法getDelay
、compareTo
。
//获取当前延迟时间(距离下次任务执行还有多久)
public long getDelay(TimeUnit unit) {
return unit.convert(time - now(), NANOSECONDS);
}
/**
-
比较this 和 other谁先执行
-
@param other
-
@return <=0 this先执行
*/
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;
}
//比较Delay
long diff = getDelay(NANOSECONDS) - other.getDelay(NANOSECONDS);
return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;
}
2、延迟执行和周期执行区别
延迟执行和周期执行区别在于period
:
-
延迟执行
period=0
。 -
周期性执行
period!=0
。 -
固定频率(
AtFixedRate
)周期性执行period>0
,每次开始执行的时间的间隔是固定的,不受任务执行时长影响。 -
固定延迟时间(
WithFixedDelay
)周期性执行period<0
,每次执行的时间受任务执行时长影响,是任务执行结束后的当前时间+ (-p)。
public boolean isPeriodic() {
return period != 0;
}
private void setNextRunTime() {
long p = period;
//AtFixedRate 当传入period > 0 时 ,每次执行的时间的间隔是固定的
if (p > 0)
time += p;
else
//WithFixedDelay 当传入period < 0 时,每次执行的时间受任务执行时长影响,是任务执行结束后的当前时间+ (-p)
time = triggerTime(-p);
}
long triggerTime(long delay) {
return now() + ((delay < (Long.MAX_VALUE >> 1)) ? delay : overflowFree(delay));
}
3、heapIndex支持快速取消定时任务
ScheduledFutureTask
还有一个变量heapIndex
,是记录任务在阻塞队列的索引的,其方便支持快速取消任务和删除任务。但是其并不会作为删除任务的位置判断,只是当用于判断惹怒是否在阻塞队列中:heapIndex >= 0
在阻塞队列中,取消任务时需要同时从阻塞队列删除任务;heapIndex < 0
不在阻塞队列中。
阻塞队列DelayedWorkQueue
的每次堆化siftUp()
、siftDown()
,以及remove()
都维护着heapIndex
,想必这也是ScheduledThreadPoolExecutor
自行定制延迟阻塞队列的原因之一。
public boolean cancel(boolean mayInterruptIfRunning) {
boolean cancelled = super.cancel(mayInterruptIfRunning);
if (cancelled && removeOnCancel && heapIndex >= 0)
//从延迟阻塞队列中删除任务
remove(this);
return cancelled;
}
4、核心逻辑run()
ScheduledFutureTask
间接实现了接口Runnable
,其核心逻辑就在run()
:
-
周期性任务,
continueExistingPeriodicTasksAfterShutdown
默认为false,意为调用shutdown()
时,会取消和阻止周期性任务的执行。 -
非周期性任务,
executeExistingDelayedTasksAfterShutdown
默认为true,意为调用shutdown()
时,不会取消和阻止非周期性任务的执行。
public void run() {
boolean periodic = isPeriodic();
//当runState为SHUTDOWN时,非周期性任务继续,周期性任务会中断取消
if (!canRunInCurrentRunState(periodic))
cancel(false);
else if (!periodic)
//非周期性任务,只执行一次
ScheduledFutureTask.super.run();
//runAndReset返回false周期性任务将不再执行
else if (ScheduledFutureTask.super.runAndReset()) {
//runAndReset() 周期性任务执行并reset
//设置下一次执行时间
setNextRunTime();
//把自己再放回延时阻塞队列
reExecutePeriodic(outerTask);
}
}
(1)canRunInCurrentRunState不同任务性质不同策略
代码一开始判断线程池的运行状态canRunInCurrentRunState
,当线程池处于SHUTDOWN状态时,是否是周期性任务有不同的策略:
-
周期性任务,
continueExistingPeriodicTasksAfterShutdown
默认为false,意为线程池被关闭时,应该取消和阻止周期性任务的执行。 -
非周期性任务,
executeExistingDelayedTasksAfterShutdown
默认为true,意为线程池被关闭时,不会取消和阻止非周期性任务的执行。
boolean canRunInCurrentRunState(boolean periodic) {
return isRunningOrShutdown(periodic ?
continueExistingPeriodicTasksAfterShutdown :
executeExistingDelayedTasksAfterShutdown);
}
/**
- False if should cancel/suppress periodic tasks on shutdown.
*/
private volatile boolean continueExistingPeriodicTasksAfterShutdown;
/**
- False if should cancel non-periodic tasks on shutdown.
*/
private volatile boolean executeExistingDelayedTasksAfterShutdown = true;
(2)单次执行调度父类FutureTask.run
延迟执行任务,即只执行一次,调用了父类的FutureTask.run()
。提交的任务如果是Runnable
型,会被包装成Callable
型作为FutureTask
的成员变量。FutureTask.run()
中直接调度执行任务的代码call()
,同时返回结果。
需要注意的是,任务代码c.call()
若抛出异常会被FutureTask
捕获处理,这样对外查找问题不利,所以最好在任务run()或者call()的核心代码用try-catch包起来。
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
//调用callable的call,并设置返回值
//如果传进来的任务是Runnable,会被转换成callable
result = c.call();
//若运行异常,ran=false,异常会被捕获处理
//所以传进来的任务的run或者call代码块最好try-catch下
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
(3)周期性执行调度父类FutureTask.runAndReset
从源码看出,周期性执行任务没有返回值,FutureTask.runAndReset
最终返回布尔值,并且也会捕获任务代码异常,最终返回true代表代码没有出现异常,下次可以正常执行,false代表任务代码中有异常,下次不能正常执行。
所以特别强调任务代码必须要try-catch
,否则一旦出现异常,周期性执行将不会再设置下次执行时间和把自己放回延迟阻塞队列。
还需要注意的是runAndReset
和单次run
的一个比较不容易注意的区别,runAndReset
的Future
的正常状态会一直是NEW
,即可通过java.util.concurrent.FutureTask#isDone
判断周期性任务是否还在正常运行。
protected boolean runAndReset() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return false;
boolean ran = false;
int s = state;
try {
Callable c = callable;
if (c != null && s == NEW) {
try {
c.call(); // don’t set result
//如果c.call抛异常,将会被处理,但是没有打印堆栈,使用者不易排查
// 不会再往下执行ran=false
//所以传进来的任务run里需要自己try-catch
ran = true;
} catch (Throwable ex) {
setException(ex);
}
}
} finally {
总结
对于面试,一定要有良好的心态,这位小伙伴面试美团的时候没有被前面阿里的面试影响到,发挥也很正常,也就能顺利拿下美团的offer。
小编还整理了大厂java程序员面试涉及到的绝大部分面试题及答案,希望能帮助到大家,
最后感谢大家的支持,希望小编整理的资料能够帮助到大家!也祝愿大家都能够升职加薪!
e;
boolean ran = false;
int s = state;
try {
Callable c = callable;
if (c != null && s == NEW) {
try {
c.call(); // don’t set result
//如果c.call抛异常,将会被处理,但是没有打印堆栈,使用者不易排查
// 不会再往下执行ran=false
//所以传进来的任务run里需要自己try-catch
ran = true;
} catch (Throwable ex) {
setException(ex);
}
}
} finally {
总结
对于面试,一定要有良好的心态,这位小伙伴面试美团的时候没有被前面阿里的面试影响到,发挥也很正常,也就能顺利拿下美团的offer。
小编还整理了大厂java程序员面试涉及到的绝大部分面试题及答案,希望能帮助到大家,
[外链图片转存中…(img-QDGEUExI-1721834275230)]
[外链图片转存中…(img-VQALHvvy-1721834275230)]
最后感谢大家的支持,希望小编整理的资料能够帮助到大家!也祝愿大家都能够升职加薪!