ScheduledThreadPoolExecutor
具备延迟执行能力的线程池,任务队列使用无界的DelayQueue。从以下几个方法来看窥探ScheduledThreadPoolExecutor。
ScheduledThreadPoolExecutor(int corePoolSize)
构造器直接调用ScheduledThreadPoolExecutor构造器,由于DelayedWorkQueue是无界的,因此ScheduledThreadPoolExecutor只有coreSize个线程数有效,其他参数Integer.MAX_VALUE, 0, NANOSECONDS
不生效。
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)
public ScheduledFuture<?> schedule(Runnable command,
long delay,
TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
RunnableScheduledFuture<?> t = decorateTask(command,
new ScheduledFutureTask<Void>(command, null,
triggerTime(delay, unit)));
delayedExecute(t);// 执行任务
return t;
}
private void delayedExecute(RunnableScheduledFuture<?> task) {
if (isShutdown())// 如果当前线程池关闭状态,拒绝任务
reject(task);
else {
super.getQueue().add(task);// 将任务放入到延迟队列
if (isShutdown() &&
!canRunInCurrentRunState(task.isPeriodic()) &&// 判断在当前状态下是否可以执行,如果为false,移除任务,取消执行当前任务
remove(task))
task.cancel(false);
else
ensurePrestart();// 正常流程走这里
}
}
void ensurePrestart() {
int wc = workerCountOf(ctl.get());
if (wc < corePoolSize)
addWorker(null, true);// 如果有核心线程数且wc小于核心线程数数量,将任务交给核心线程执行
else if (wc == 0)
addWorker(null, false);// 如果核心线程数为0,新起一个线程从延迟队列中拿到任务,并执行任务
}
// 线程从队列中获取任务时,如果没有满足当前时间周期的任务时,获取任务失败。因此延迟队列DelayQueue控制来时间周期.内部使用数组实现了小顶堆,将最先执行的任务放在前面
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();// 可响应中断的方式进行加锁等待
try {
for (;;) {
E first = q.peek();
if (first == null)
available.await();//说明堆中没有任务
else {
long delay = first.getDelay(NANOSECONDS);//用下一次执行时间time-now得到结果,如果小于0,说明需要执行任务
if (delay <= 0)
return q.poll();// 返回任务,从ThreadPoolExecutor的getTask()中返回
first = null; // 如果没有到达下一次任务的执行时间
if (leader != null)
available.await();// 堆中不是最早执行的其他线程在此等待,等待被堆中最先执行的任务线程唤醒
else {
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
available.awaitNanos(delay);// 堆中最先执行的那个任务线程在此等待
} finally {
if (leader == thisThread)
leader = null;// 判断当前唤醒的线程是否是第一个进来的线程,如果是,需要唤醒后继线程,进入for循环,再次等待在available.awaitNanos(delay)这里,等待超时唤醒
}
}
}
}
} finally {
if (leader == null && q.peek() != null)
available.signal();// leader线程唤醒后继线程
lock.unlock();// 释放锁,在此进入for循环,此时delay<=0成立,执行return q.poll(),从ThreadPoolExecutor的getTask()返回,执行run方法
}
}
// 线程最终调用ScheduledFutureTask的run方法
public void run() {
boolean periodic = isPeriodic();// 是否是周期性任务
if (!canRunInCurrentRunState(periodic))// 当前线程池状态是否可以执行
cancel(false);
else if (!periodic)// 如果不是周期性任务,直接调用run方法,执行一次
ScheduledFutureTask.super.run();
else if (ScheduledFutureTask.super.runAndReset()) {//runAndReset()执行run方法,并且恢复属性至原始状态
setNextRunTime();// 设置下次执行时间
reExecutePeriodic(outerTask);// 重新将任务放入延迟队列中
}
}
// 设置下次执行时间,如果p>0,说明间隔时间从触发时间开始算。如果p<0说明间隔时间从任务结束时间开始算。scheduleAtFixedRate方法的p>0,而scheduleWithFixedDelay的p<0,这就造成两个方法的执行结果不一致
private void setNextRunTime() {
long p = period;
if (p > 0)
time += p;
else
time = triggerTime(-p);
}
思考
核心线程数设置为1有什么风险?
如果同一时间需要执行的任务数大于1个,那么同一时间只会有一个任务在执行,其他的任务不执行。
因此,延迟任务的核心线程数大小需要考虑任务数量和任务执行频率。
new ScheduledThreadPoolExecutor(1);
demo
public void test02() {
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1);
executor.scheduleAtFixedRate(()->{
System.out.println(new Date() + ":1");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, 0, 1, TimeUnit.SECONDS);
executor.scheduleAtFixedRate(()->{
System.out.println(new Date() + ":2");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, 0, 1, TimeUnit.SECONDS);
executor.scheduleAtFixedRate(()->{
System.out.println(new Date() + ":3");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, 0, 1, TimeUnit.SECONDS);
}
针对上述Demo解决方案
可以将ScheduledThreadPoolExecutor当作分发线程池,任务最终交给其他线程执行。
xxl-job里也可以看到这个用法,定时从数据库获取下N个任务触发时间,交给其他线程池,其他线程池在for死循环中判断任务是否可执行。
这样的好处:
1. 由于ScheduledThreadPoolExecutor是无界队列,存在风险。交给其他线程池执行可以控制队列长度以及拒绝策略,更加安全
2. 不需要担心ScheduledThreadPoolExecutor的核心线程数不够的问题
public void test03() {
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();// 使用自定义线程池
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1);
executor.scheduleAtFixedRate(()->{
cachedThreadPool.execute(()->{
System.out.println(new Date() + ":1");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}, 0, 1, TimeUnit.SECONDS);
executor.scheduleAtFixedRate(()->{
cachedThreadPool.execute(()->{
System.out.println(new Date() + ":2");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}, 0, 1, TimeUnit.SECONDS);
executor.scheduleAtFixedRate(()->{
cachedThreadPool.execute(()->{
System.out.println(new Date() + ":3");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}, 0, 1, TimeUnit.SECONDS);
}