了解了JDK自带的Timer和TimerTask的源码,再了解ScheduledThreadPool就会相对容易一些。
ScheduledThreadPoolExecutor继承了ThreadPoolExecutor。我们测试的方法如下:
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
scheduledThreadPool.scheduleWithFixedDelay(new Runnable(){
@Override
public void run() {
System.out.println("aaaaaa");
}
}, 5, 1000, TimeUnit.MILLISECONDS);
ScheduledThreadPoolExecutor的构造方法如下所示,调用了父类ThreadPoolExecutor的构造方法这里可以看到他使用的队列是DelayedWorkQueue,等下我们要会分析这个队列的存取方法offer和tack。
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, TimeUnit.NANOSECONDS,
new DelayedWorkQueue());
}
接下来我们来分析一下这个ScheduledThreadPoolExecutor的scheduleWithFixedDelay方法,代码如下:
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
if (delay <= 0)
throw new IllegalArgumentException();
ScheduledFutureTask<Void> sft =
new ScheduledFutureTask<Void>(command,
null,
triggerTime(initialDelay, unit),
unit.toNanos(-delay));
RunnableScheduledFuture<Void> t = decorateTask(command, sft);
sft.outerTask = t;
delayedExecute(t);
return t;
}
这里看到他对我们传入的任务和调度时间的等参数进行了进一步的封装,构造了一个ScheduledFutureTask类的对象,这个类有以下关键的方法,他实现的接口RunnableScheduledFuture定义如下:
public interface RunnableScheduledFuture<V> extends RunnableFuture<V>, ScheduledFuture<V>
他继承了ScheduledFuture类,再看看这个类继承了Delayed这个类,而这个类继承了extends Comparable<Delayed>类,所以ScheduledFutureTask中实现了compareTo方法,这个方法就是执行的任务
优先级比较的具体实现,我们先往后接着看,等用到这个方法的时候在具体解释。
接下来对我们要执行的任务(实现Runnable接口的任务)进行了进一步的装饰,传入了任务command和刚刚构造好的包装类ScheduledFutureTask,decorateTask方法代码如下所示:
protected <V> RunnableScheduledFuture<V> decorateTask(
Runnable runnable, RunnableScheduledFuture<V> task) {
return task;
}
他直接返回类这个ScheduledFutureTask包装类,但是接受的实际类型是RunnableScheduledFuture,RunnableScheduledFuture是ScheduledFutureTask的父类。
然后将ScheduledFutureTask的outerTask属性设置为装置好的RunnableScheduledFuture类的实例,这个实例就是ScheduledFutureTask自己。
接下来调用delayedExecute方法,将刚装饰好的RunnableScheduledFuture实例传入,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
ensurePrestart();
}
}
他首先将调用父类的方法拿到DelayedWorkQueue对象(这个对象是构造ScheduledThreadPoolExecutor的时候调用父类的构造方法传入的),将包装好的对象放入到队列里面,回想TImer的实现,我们猜想刚提交的任务的
执行优先级顺序肯定是在加入这个队列里面的时候来计算的,将优先级最高的任务设置为第一个,等下我们来分析这个队列是怎么保证优先级的,接着往下看,入队成功后调用ensurePrestart方法,这个方法的代码如下:
void ensurePrestart() {
int wc = workerCountOf(ctl.get());
if (wc < corePoolSize)
addWorker(null, true);
else if (wc == 0)
addWorker(null, false);
}
似曾相识的赶脚啊,调用了addWorker方法,这个方法我们在分析线程池ThreadPoolExecutor的时候分析过,注意这里传入的第一个参数是null也就是Worker对象的firstTask是null,
这个很好理解,我们传入的任务并不是立即执行的,这也印证了Worker的firstTask并不一定不是null,如果是null的话他就去调用DelayedWorkQueue的take去取。分析到这里提交任务的流程就分析的差不多了。
下面我们就要看看DelayedWorkQueue的实现了,首先分析一下offer方法(上面入队的时候调用的add实际上也是调用的这个方法),代码如下:
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;
}
可以看到如果提交的任务如果是第一个的话就将他放到队列的头并且调用Condition对象的signal方法唤醒上面创建的在take方法上阻塞的线程,如果不是第一个任务就说明队列中有其他的任务,
这就需要进行优先的判断,调用siftUp这个方法,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);
}
这个方法和Timer中TaskQueue队列的fixUp方法类似,从尾部的任务折半看是比较,怎么比较的呢,调用的就是compareTo得这个方法,上文我们提到过,这个方法就是真正决定了任务的优先级,由ScheduledFutureTask类
实现的他就是根据任务传入的启动时间来比较,将需要入队的任务放到一个合适的位置上。
接下来分析一下DelayedWorkQueue的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(TimeUnit.NANOSECONDS);
if (delay <= 0)
return finishPoll(first);
else 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();
}
}
和我们想象的一样这个方法是一个自旋的方法,如果取得队列的头是null就一直等待被唤醒,被唤醒之后就重新拿到任务并判断要执行的时间和当前时间的差值,限时等待一段时间之后返回这个任务待后续方法去执行。