1.ScheduledThreadPoolExecutor的原理
ScheduledThreadPoolExecutor本质上还是ThreadPoolExecutor,所以调度上还是会执行ThreadPoolExecutor的流程,只是在几个地方有所不同,主要是因为DelayedWorkQueue和ScheduledFutureTask。
case1:当core线程数不够的时候执行schedule
ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(1);
long start = System.currentTimeMillis();
scheduledExecutorService.schedule(new Runnable() {
@Override public void run() {
LOG.info("{}, task 1 delayed:{}", System.currentTimeMillis() - start, Thread.currentThread().getName());
Uninterruptibles.sleepUninterruptibly(10, TimeUnit.SECONDS);
}
}, 2, TimeUnit.SECONDS);
long start2 = System.currentTimeMillis();
scheduledExecutorService.schedule(new Runnable() {
@Override public void run() {
LOG.info("{}, task 2 delayed:{}", System.currentTimeMillis() - start2,
Thread.currentThread().getName());
}
}, 5, TimeUnit.SECONDS);
结果:
因为core设置为1,第一个任务占用了唯一的core线程,按照线程池调度策略,第二个任务进入了队列,等待第一个任务执行完。如果第一个任务执行时间过长,比如这个case中的执行了10s,第二个任务等待后调度执行的时机就已经不准确。下面是核心代码:
public RunnableScheduledFuture<?> take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
for (;;) {
RunnableScheduledFuture<?> first = queue[0];
if (first == null)
available.await();
else {
long delay = first.getDelay(NANOSECONDS);
if (delay <= 0)
return finishPoll(first);
first = null; // don't retain ref while waiting
if (leader != null)
available.await();
else {
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
available.awaitNanos(delay);
} finally {
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
if (leader == null && queue[0] != null)
available.signal();
lock.unlock();
}
}
上面第一个task对应的runnable,即task1,走的逻辑分支是
else {
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
available.awaitNanos(delay);
} finally {
if (leader == thisThread)
leader = null;
}
}
delay是2秒,正常等待2秒后执行,执行的任务因为有10秒的sleep所以时间比较久,超过了task2的等待时间。
task1结束之后,回到父类核心的runWorker方法中
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
继续getTask,继续调用DelayedWorkQueue的take方法,第二次走到的分支是
else {
long delay = first.getDelay(NANOSECONDS);
if (delay <= 0)
return finishPoll(first);
这里delay是负数,所以会立即执行,但也已为时已晚,比应该的执行时间晚了7秒钟。
如果这里把case写成
ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(2);
long start = System.currentTimeMillis();
scheduledExecutorService.schedule(new Runnable() {
@Override public void run() {
LOG.info("{}, task 1 delayed:{}", System.currentTimeMillis() - start, Thread.currentThread().getName());
Uninterruptibles.sleepUninterruptibly(10, TimeUnit.SECONDS);
}
}, 2, TimeUnit.SECONDS);
long start2 = System.currentTimeMillis();
scheduledExecutorService.schedule(new Runnable() {
@Override public void run() {
LOG.info("{}, task 2 delayed:{}", System.currentTimeMillis() - start2,
Thread.currentThread().getName());
}
}, 5, TimeUnit.SECONDS);
就不会出现上述问题了,因为task2的时候core线程没有满,所以会再拉起一个core线程去执行task2,即使task1还在占用第一个core线程,执行结果如下:
看到task2被线程2执行了,执行时间是在预期的5秒之后。
case2:当core线程数不够的时候执行scheduleAtFixedRate
ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(2);
long start = System.currentTimeMillis();
AtomicInteger integer = new AtomicInteger(0);
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override public void run() {
LOG.info("{}, task {} delayed:{}", Thread.currentThread().getName(), integer.addAndGet(1),
System.currentTimeMillis() - start);
Uninterruptibles.sleepUninterruptibly(4, TimeUnit.SECONDS);
}
}, 0, 2, TimeUnit.SECONDS);
结果:
这里和执行schedule不太一样,区别在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);
}
}
这里会走最后一个else if分支,执行runAndReset,会调用setNextRunTime和reExecutePeriodic
private void setNextRunTime() {
long p = period;
if (p > 0)
time += p;
else
time = triggerTime(-p);
}
这里因为第一个task执行时间为4秒,超过间隔2秒,所以time+p已经是过去时了,
if (canRunInCurrentRunState(true)) {
super.getQueue().add(task);
if (!canRunInCurrentRunState(true) && remove(task))
task.cancel(false);
else
ensurePrestart();
}
然后这时才会把task加入到队列里,但long delay = first.getDelay(NANOSECONDS);,这里delay按上面说法,已经是过去时了,是负数,所以会立即执行,和case1一样已为时已晚。
和case1不同的是,这里的core即使是2,也于事无补,因为新的task只会在老的task结束之后才会补充到queue里,可以说是输在了起跑线上。
还有一点,因为在delayedExecute的时候ensurePrestart,所以core线程提前被启动了,所以不会再启动第二个core线程,除非再开一个scheduleAtFixedRate的任务,所以也没法通过扩大core来增加并行。