在上篇博客《源码分析与实战——深入理解Java的4种线程池》中,我们详细分析了一下Java四种线程池的基本源码,编写代码进行了尝试。其中single单线程池、fiexed定长线程池、cached缓存线程池都比较简单,scheduled线程池则复杂一些。今天我们结合延迟队列来对它进行源码分析,详细讲解一下延时执行线程池的工作原理。
首先,我们还是再来看一下最简单的一个使用示例:
public class TestSchedule {
public static void main(String[] args) {
//此时不可再用ExecutorService
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
System.out.println("scheduledExecutorService start ...");
//延时任务
scheduledExecutorService.schedule(() -> System.out.println("exec a task ..."), 1, TimeUnit.SECONDS);
System.out.println("scheduled a task ...");
}
}
执行结果:
scheduledExecutorService start ...
scheduled a task ...
exec a task ...
我们可以注意到,这里我们使用的是ScheduledExecutorService,其他3个线程池都是直接用ExecutorService即可。
然后再次看看Executors.newScheduledThreadPool()方法:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
为了实现延时执行线程池,Java专门定义了一个新类ScheduledThreadPoolExecutor去实现它。
类定义如下:
public class ScheduledThreadPoolExecutor
extends ThreadPoolExecutor
implements ScheduledExecutorService {
......
}
它有一个父类ThreadPoolExecutor,实现了接口ScheduledExecutorService。
ScheduledThreadPoolExecutor类继承了ThreadPoolExecutor类,它是实现线程池的主类,其他3种线程池都是直接利用这个类来实现的。我们甚至可以利用它来扩展自定义的新的线程池,这个以后有机会再单独讲。
我们看看new ScheduledThreadPoolExecutor(corePoolSize);
这里的内容在上篇博文中其实我已经详细讲过了,但是本篇我觉得应该也可以独立阅读,所以重复一下,看过那篇的朋友可以跳过。
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
ScheduledThreadPoolExecutor调用了父类的构造方法,也就是ThreadPoolExecutor的构造方法:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
最终实际调用的是:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
......
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
通过这个调用链,我们得到的结果是:
这个构造过程,很明显只是一个初始化过程!
ScheduledThreadPoolExecutor类实现了ScheduledExecutorService接口:
public interface ScheduledExecutorService extends ExecutorService {
public ScheduledFuture<?> schedule(Runnable command,
long delay, TimeUnit unit);
public <V> ScheduledFuture<V> schedule(Callable<V> callable,
long delay, TimeUnit unit);
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit);
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit);
}
很明显,我们操作这个线程池的时候,使用的就是这4个接口方法,因此我们获取newScheduledThreadPool()的结果的时候,用的是ScheduledExecutorService:
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
ScheduledThreadPoolExecutor类重写了这4个接口方法,结合ThreadPoolExecutor类中的方法,实现了延时执行的功能。
schedule方法,是延时执行线程池使用最多的方法,源代码如下:
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;
}
这里面就2个核心点:
decorateTask只是个包装类,可以不用管它,关键是这个新建了一个 ScheduledFutureTask的对象。这个类是ScheduledThreadPoolExecutor的一个内部类,它的作用是创建一个延时执行的任务。这个任务会在什么时候执行,由triggerTime(delay, unit)指定:
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));
}
从上面源码可以看出,它返回的是一个时间,从now()当前时间开始往后推delay,即我们加入任务时,所指定的延时时间。总而言之,triggerTime()返回了这个任务执行的时间时间,这是一个绝对时间。
我们再看这里所调用的ScheduledFutureTask构造方法:
ScheduledFutureTask(Runnable r, V result, long ns) {
super(r, result);
this.time = ns;
this.period = 0;
this.sequenceNumber = sequencer.getAndIncrement();
}
很简单,这里只有几个赋值,只是最开始的初始化。
它只是把这个新任务执行的绝对时间赋值给了类的成员time,这个延时任务是一次性执行完成的,所以period为0。
第二步就是调用了:delayedExecute(t);
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
ensurePrestart();
}
}
通过源码,可以清晰的看到:任何情况下添加任务,第一步就是执行:
super.getQueue().add(task);
无论队列空的、还是工作线程空闲,系统其实什么都不管,先把新任务加入队列再说!因此,很多相关的文档是这样描述的:“如果当前线程池中没有空闲的线程,就将新任务放入等待队列"、"第一个任务加入时,会创建一个新线程,去执行这个任务”,等等,这些都是不正确的说法。实际是无论什么情况,新任务都是先加入队列再说!只不过,当前线程池里面有工作线程空闲或者线程数没有达到指定数量时,这个新任务会很快从队列里面取出并执行。后面从源代码可以很简单的看出来!
这里面getQueue(),取得是初始化时候创建的DelayedWorkQueue队列,我们看一下这个队列的add()方法:
public boolean add(Runnable e) {
return offer(e);
}
public boolean offer(Runnable x) {
if (x == null)
throw new NullPointerException();
RunnableScheduledFuture<?> e = (RunnableScheduledFuture<?>)x;
final ReentrantLock lock = this.lock;
lock.lock();
try {
int i = size;
if (i >= queue.length)
grow();
size = i + 1;
if (i == 0) {
queue[0] = e;
setIndex(e, 0);
} else {
siftUp(i, e);
}
if (queue[0] == e) {
leader = null;
available.signal();
}
} finally {
lock.unlock();
}
return true;
}
逻辑很简单:
queue是什么呢?我们也可以看一下:
private static final int INITIAL_CAPACITY = 16;
private RunnableScheduledFuture<?>[] queue =
new RunnableScheduledFuture<?>[INITIAL_CAPACITY];
queue是一个数组,初始大小为16。也就说,线程池的阻塞队列默认长度是16,可以放入16个任务。
然后再看看grow()方法:
private void grow() {
int oldCapacity = queue.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // grow 50%
if (newCapacity < 0) // overflow
newCapacity = Integer.MAX_VALUE;
queue = Arrays.copyOf(queue, newCapacity);
}
当队列容量不够时,直接将队列长度扩大50%!也就是说,这也是一个无界队列,使用不当可能会导致队列里任务过多而OOM!
再看看siftUp()方法:
private void siftUp(int k, RunnableScheduledFuture<?> key) {
while (k > 0) {
int parent = (k - 1) >>> 1;
RunnableScheduledFuture<?> e = queue[parent];
if (key.compareTo(e) >= 0)
break;
queue[k] = e;
setIndex(e, k);
k = parent;
}
queue[k] = key;
setIndex(key, k);
}
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;
}
(k-1)>>>1,即将k减1后无符号右移1位,也就是减1后除以2。假设当前k的值为33,那么parent就是16。拿当前任务和数组下标为16的任务进行比较,比的是2个任务对象里面time的大小,time越大,执行时间越晚,因此逻辑是:
key.compareTo(e) >= 0意味着当前任务比parent对应的任务更晚,所以位置不动,直接break出来;否则,通过while循环不停的向上查找,直到找到一个合适的位置为止。在这个过程中,每一次比较都会交换2个任务的位置。通过这种方式,将新任务插入到合适的位置。大家可以看到,这个不是一个逐个比较的过程,其实是二分查找,目的是为了提高效率。
提个问题:假设queue[33]的time是100,queue[32]的time是200,queue[16]的time是50,那么会发生什么事情?
此时,我们发现key.compareTo(e) >= 0成立,break执行了,没有进行任何交换直接跳过!如果这个队列顺序要求很精确,那么queue[33]应该比queue[32]先执行吗?这个问题内容稍多,我放到下一篇单独讲。
接下来就是执行ensurePrestart();
void ensurePrestart() {
int wc = workerCountOf(ctl.get());
if (wc < corePoolSize)
addWorker(null, true);
else if (wc == 0)
addWorker(null, false);
}
在上篇博文中,其实我已经详细介绍它的作用了,就是在线程池启用初期,当工作线程数量没有达到指定数量时,每一次都会新建一个线程去执行任务。注意到这里的addWorker第一个参数都是null,意味着新线程都是从等待队列头获得任务。addWorker方法在上篇也已经详细讲过,不在重复了。
到这一步为止,schedule加入任务已经完成,接下来看看工作线程如何取任务并执行的。
在addWorker方法中,我们可以看到几条关键语句:
private boolean addWorker(Runnable firstTask, boolean core) {
......
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
...
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
...
}
return workerStarted;
}
新的工作线程就是Worker实例,这是定义在ThreadPoolExecutor里面的一个内部类,专门用作线程池工作线程。
此处t.start(),其实就是开始启动线程运行worker里面定义的run()方法。
我们摘取一下其中一些关键部分:
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
final Thread thread;
Runnable firstTask;
......
public void run() {
runWorker(this);
}
}
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) {
w.lock();
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
runWorker()是线程池执行最关键的一个方法。这段代码实质就做了2件事:确定待执行的任务;然后执行任务。
如何确定该执行哪个任务,代码里面有2条语句提供了2种途径:
Runnable task = w.firstTask;
firstTask其实是从Worker构造函数传递进来的。这条语句对于延时执行线程池其实无用,只针对其他3种线程池有效。这里的firstTask,仅仅在其他3种线程池启动初期,直接将任务交给新线程去处理的场景。延时执行线程池这里的firstTask都是null。
while (task != null || (task = getTask()) != null)
通过getTask()方法获得一个任务,实质是从等待队列中获取一个任务,主要源代码如下:
private Runnable getTask() {
......
for (;;) {
......
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
......
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
这里是一个死循环,主要调用了2个方法:poll()和take()。通过timed定义可以知道,对于延时线程池,timed肯定是false,因此实际调用的是
workQueue.take();
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();
}
}
first = queue[0];可以看出,take()取到的是queue队列的第一个任务。当first为空的时候(等待队列没有任务待执行),available.await();被执行,线程进入休眠。上面代码讲过,加入新任务到队列时,会调用available.signal();发送信号,唤醒休眠中的线程。finishPoll()方法非常关键,我们放到下篇讲解。
执行从队列中取到的任务,其实就是一条语句:
task.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);
}
}
无论是不是周期性任务,都是使用这同一个run()方法。
忽略次要代码,核心就是2段代码:
public void run() {
......
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
......
try {
result = c.call();
ran = true;
} catch (Throwable ex) {
......
}
......
}
} finally {
......
}
}
其实关键就是这个call,执行的就是我们任务中定义的方法体。
setNextRunTime()根据period设置下次运行的时间:
private void setNextRunTime() {
long p = period;
if (p > 0)
time += p;
else
time = triggerTime(-p);
}
reExecutePeriodic(outerTask);
void reExecutePeriodic(RunnableScheduledFuture<?> task) {
if (canRunInCurrentRunState(true)) {
super.getQueue().add(task);
if (!canRunInCurrentRunState(true) && remove(task))
task.cancel(false);
else
ensurePrestart();
}
}
可以看到,super.getQueue().add(task);又被执行了,任务又被放回等待队列,等待下一次执行。
如果需要重复周期性的执行某个任务,可以使用scheduleAtFixedRate接口。
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit) {
......
ScheduledFutureTask<Void> sft =
new ScheduledFutureTask<Void>(command,
null,
triggerTime(initialDelay, unit),
unit.toNanos(period));
RunnableScheduledFuture<Void> t = decorateTask(command, sft);
sft.outerTask = t;
delayedExecute(t);
return t;
}
从源代码可以看出,代码和schedule基本一样,唯一区别在于ScheduledFutureTask多给了一个period参数。后面的机制基本一模一样,如何重复执行的,上面也讲过了,就是执行完之后放回阻塞队列,等待下一次执行。
整个Scheduled线程池的源码分析基本都完成了,经过这次梳理,感觉我自己收获也挺大的。
通过本篇的分析,对延时执行线程池进行总结一下: