ThreadPoolExecutory与ScheduledThreadPoolExecutor对比
一个线程池必不可少两大部分,一个存放线程的容器(HashSet
),一个存放任务的队列(Queue
)
ThreadPoolExecutor
1.由于工作队列是多线程共享,也就是说线程容器中的线程都需要访问,所以这个队列必须是线程安全的
2.由于工作线程在没有任务执行的时候,那么需要阻塞,所以,这个队列要支持没有任务的时候让线程阻塞
综上得知,BlockingQueue
满足条件
ScheduledThreadPoolExecutor
1.由于线程池需要任务调度,且需要延迟执行,那么推出任务必须能够排队执行,也就是说延迟2s执行的任务就算是最后放入的,也要比延迟5s执行的任务先执行,需要队列能够进行排序。
2.由于放入的任务,不一定能够立即执行,所以还是需要得放入队列,然后获取,看看是否满足执行条件,时间是否满足
结论
1.ThreadPoolExecutor
原有部分逻辑不满足此需求,所以需要增加自己的执行逻辑
2.由于任务需要排队,需要能够排序,那么需要实现自己的BlockingQueue
简单使用的区别
scheduleAtFixedRate()
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit) {
command
任务
initialDelay
刚开始延迟多长时间执行 单位由第四个参数决定
period
每隔多长时间执行
unit
单位
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1);
executor.scheduleAtFixedRate(() -> {
System.out.println(LocalDateTime.now() + " -> 1");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, 0, 2, TimeUnit.SECONDS);
可以看出每次执行间隔时间是由每次任务执行最大的时间决定的
scheduleWithFixedDelay()
executor.scheduleWithFixedDelay(() -> {
System.out.println(LocalDateTime.now() + "2");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, 0, 2, TimeUnit.SECONDS);
可以看出每次任务执行间隔时间是由执行任务的时间加设置的周期时间决定的
提交任务三大方式
1.
schedule
()
2.scheduleAtFixedRate
()
3.scheduleWithFixedDelay
()
我们对比了下这三个方法,里面都用到了decorateTask
(),接口返回结果是RunnableScheduledFuture
接口,实现类是ScheduledFutureTask
RunnableScheduledFuture
接口
public interface RunnableScheduledFuture<V> extends RunnableFuture<V>, ScheduledFuture<V> {
// 判断是否是周期性任务
boolean isPeriodic();
}
ScheduledFutureTask
实现类
主要成员变量
private class ScheduledFutureTask<V>
extends FutureTask<V> implements RunnableScheduledFuture<V> {
// 序列号
private final long sequenceNumber;
// 任务开始的时间
private long time;
// 任务执行的间隔时间
private final long period;
run
方法介绍
public void run() {
// 判断是否是周期任务
boolean periodic = isPeriodic();
// 判断是否能执行当前任务
if (!canRunInCurrentRunState(periodic))
cancel(false);
// 不是周期任务执行父类的run方法
else if (!periodic)
ScheduledFutureTask.super.run();
else if (ScheduledFutureTask.super.runAndReset()) {
// 设置下次的执行时间
setNextRunTime();
// 重复执行任务
reExecutePeriodic(outerTask);
}
}
1.如果当前线程池运行状态不可以执行任务,取消该任务,然后直接返回
2.如果不是周期性任务,调用FutureTask
中的run
方法执行,会设置执行结果,然后直接返回
3.如果是周期性任务,调用FutureTask
中的runAndReset
方法执行,不会设置执行结果,然后直接返回
4.计算下次执行该任务的具体时间;
5.重复执行任务。
setNextRunTime
介绍
private void setNextRunTime() {
long p = period;
if (p > 0)
time += p;
else
time = triggerTime(-p);
}
schedule
介绍
public <V> ScheduledFuture<V> schedule(Callable<V> callable,
long delay,
TimeUnit unit) {
// 校验参数
if (callable == null || unit == null)
throw new NullPointerException();
// 装饰任务
RunnableScheduledFuture<V> t = decorateTask(callable,
new ScheduledFutureTask<V>(callable,
triggerTime(delay, unit)));
// 执行任务
delayedExecute(t);
return t;
}
delayedExecute
介绍
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
// 这里是增加一个worker线程,避免提交的任务没有worker去执行
// 原因就是该类没有像ThreadPoolExecutor一样,woker满了才放入队列
ensurePrestart();
}
}
scheduleAtFixedRate与scheduleWithFixedDelay任务执行时间间隔差异分析
period
一个传的正数,一个传的负数,执行任务的时候会调用setNextTime
方法
这样就清楚一个取最大时间,一个取任务执行时间+设置的周期时间的原因了