ScheduledThreadPoolExecutor是java自带的用来设置延时任务的线程池,我们下来看看他是如何处理延时任务的。
public class ScheduledThreadPoolExecutor
extends ThreadPoolExecutor
implements ScheduledExecutorService
他继承自ThreadPoolExecutor。所以总体的流程是和ThreadPoolExecutor一样的。有核心线程数,有队列,有最大线程数。
因为是延时或者定时任务。所以这些任务不会立马执行,所以在提交的流程上区别于ThreadPoolExecutor。
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;
}
第一步先转化成ScheduledFutureTask。
private final long sequenceNumber;
private long time;
private final long period;
RunnableScheduledFuture<V> outerTask = this;
int heapIndex;
period是给定时任务记录的间隔。
time是算好的执行时间。
sequenceNumber是用来排序的,当执行的时间一样的,sequenceNumber小的会优先执行。
heapIndex是用来取消的索引。这个主要是用来取消任务的。
然后就把任务放在queue里,并且尝试增加线程池里的线程数。
在线程池启动线程后的流程是一样的。线程启动后会去从阻塞队列里获取任务。
ScheduledThreadPoolExecutor的阻塞队列是一个固定的类DelayedWorkQueue。
DelayedWorkQueue是一个优先队列的实现,这样就保证了第一个元素是最快需要执行的任务,就是我们上面ScheduledFutureTask的time和sequenceNumber。
这个队列的不同之处就在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();
}
}
如果有第一个元素,就看是不是可执行,执行就是看记录的时间和当前时间对比,如果差值小于等于0就直接移动给线程执行。
public long getDelay(TimeUnit unit) {
return unit.convert(time - now(), NANOSECONDS);
}
如果不能执行,就先看leader是否存在,存在的话就直接await。这里的leader的作用是用来自我唤醒。leader线程的是awaitNanos。而不是直接await。这样就能保证总有一个线程在观察变化,当没有新增任务的时候,leader就会在awaitNanos之后,再次查看状态,此时原来的任务已经是可以执行的了,等待的时间到了。这里只要还有任务,就会去做 available.signal();因为这时候能执行的任务不止一个,再去唤醒一个线程。
如果中间新增了一个任务。在offer的时候会做一个判断
if (queue[0] == e) {
leader = null;
available.signal();
}
如果新增的一个是最小值。就会把leader设置为null,然后唤醒一个线程。这样被唤醒的线程,判断状态的时候,leader是null,他就会成为leader。这里设置了leader之后,可以减少很多无用的空转。
如果不设置,那么所有线程都会去记录自己等待的时间,其实执行的任务只有一个,其他的自动唤醒后都是空转一次。
Thread designated to wait for the task at the head of the queue. This variant of the Leader-Follower pattern (http://www.cs.wustl.edu/~schmidt/POSA/POSA2/) serves to minimize unnecessary timed waiting. When a thread becomes the leader, it waits only for the next delay to elapse, but other threads await indefinitely. The leader thread must signal some other thread before returning from take() or poll(…), unless some other thread becomes leader in the interim. Whenever the head of the queue is replaced with a task with an earlier expiration time, the leader field is invalidated by being reset to null, and some waiting thread, but not necessarily the current leader, is signalled. So waiting threads must be prepared to acquire and lose leadership while waiting.
jdk的文档中详细的介绍了Leader-Follower模式的好处
共同学习,写下你的评论
评论加载中...
作者其他优质文章