ScheduledThreadPoolExecutor

547 阅读6分钟

ScheduledThreadExecutor是在TPE的基础上新增了让任务在指定延迟以后执行,或者周期性执行的功能。它比Timer更加灵活,可以多线程等等。

延迟的任务在被允许之前不能执行,但是不能保证,在允许之后多久能真正执行到。任务按照提交顺序的FIFO顺序调度。当有任务在运行之前被取消,执行将会被停止。默认情况下此时任务不会从任务队列中移除,除非到了延迟的时间。通过设置setRemoveOnCancelPolicy可以让任务在被取消的时候从队列移除。

一个任务的执行通过scheduleAtFixedRate或者scheduleWithFixedDelay调度到,不会重合。但是不同的调度可能由不同的线程执行,在前面执行的在内存可见性意义上happen-before后面的执行。

虽然这个类继承自TPE,但是一些方法不会用到,比如,这个类拥有的是固定大小的线程池,并且有一个无界的队列。

STPE类重写了TPE的execute(Runnable)方法和AbstractExecutorService的submit(Runnable)方法,用来生成ScheduledFuture对象来控制每个任务的延迟和调度。并且,后面继承的类不能改写这个方法(只能使用父类的逻辑)。但是提供了decorateTask方法来定制具体的任务类型。(customize the concrete task types used to execute commands entered execute等方法)

STPE对TPE做了如下改变

  • 使用自定义的任务类型ScheduledFutureTask来代表任务,即使这些任务不需要调度执行(比如是通过TPE的execute的方法调用的,而不是ScheduledExecutorService的方法)。这些任务被视为延迟为0的任务。
  • 使用了自定义的队列DelayedWorkQueue,是无界的DelayQueue的一个变种。
  • 支持run-after-shutdown参数。能覆盖shutdown方法来让任务在shutdown以后不再执行

构造参数

public ScheduledThreadPoolExecutor(int corePoolSize,
                                       ThreadFactory threadFactory,
                                       RejectedExecutionHandler handler) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue(), threadFactory, handler);
    }

就是TPE的构造方法,不再赘述。

提交任务的方法

  1. 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;
    }
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;
    }
  1. schedule周期性执行
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit) {
        if (command == null || unit == null)
            throw new NullPointerException();
        if (period <= 0)
            throw new IllegalArgumentException();
        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;
    }
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;
    }
  1. execute方法
public void execute(Runnable command) {
        schedule(command, 0, NANOSECONDS);
    }
  1. submit方法
public Future<?> submit(Runnable task) {
        return schedule(task, 0, NANOSECONDS);
    }
public <T> Future<T> submit(Runnable task, T result) {
        return schedule(Executors.callable(task, result), 0, NANOSECONDS);
    }
public <T> Future<T> submit(Callable<T> task) {
        return schedule(task, 0, NANOSECONDS);
    }

无论是什么提交任务的方法,都离不开ScheduledFutureTask这个类。

private class ScheduledFutureTask<V>
            extends FutureTask<V> implements RunnableScheduledFuture<V>

这个类是是FutureTask和RunnableScheduledFuture的子类,看他的构造方法

ScheduledFutureTask(Runnable r, V result, long ns) {
            super(r, result);
            this.time = ns;
            this.period = 0;
            this.sequenceNumber = sequencer.getAndIncrement();
        }
ScheduledFutureTask(Runnable r, V result, long ns, long period) {
            super(r, result);
            this.time = ns;
            this.period = period;
            this.sequenceNumber = sequencer.getAndIncrement();
        }
ScheduledFutureTask(Callable<V> callable, long ns) {
            super(callable);
            this.time = ns;
            this.period = 0;
            this.sequenceNumber = sequencer.getAndIncrement();
        }

都会调到父类的方法,那就看一下父类FutureTask。FutureTask是Future类的子类。Future描述了一个异步计算,通过get()方法能够获得计算结果(否则阻塞在这里)。另外,Future也支持cancel。Future支持泛型T,表示返回结果的类型。如果只是想用Future表示一个可cancel的计算,并不关心返回值,可以将Future定义为Future<?>,并且将任务的计算结果返回null.FutureTask是Future的一种实现,并且可以被Executor执行

FutureTask<String> future = new FutureTask<String>(new Callable<String>() {
    public String call() {
        return "hello world";
    }
});
//因此FutureTask实现了Runnable接口
executor.execute(future);

关于FutureTask的实现后面有机会再介绍。ScheduledFutureTask相对于父类FutureTask多了几个成员变量

  • int sequenceNumber
  • long time, 代表任务允许执行的时间
  • long period, 代表任务重复的周期。period>0表示固定速率执行,period<0表示固定延迟执行。
  • RunnableScheduledFuture outerTask
  • int heapIndex

通过time和period可以控制周期性任务下次执行的时间

private void setNextRunTime() {
            long p = period;
            if (p > 0)
                //在上一次执行的基础上
                time += p;
            else
                //只管触发时间,不用关心上次执行时间
                time = triggerTime(-p);
        }
long triggerTime(long delay) {
        return now() +
            ((delay < (Long.MAX_VALUE >> 1)) ? delay : overflowFree(delay));
    }

值得注意的是,SFT有一个compareTo方法,(谁的time小谁排在前面?)

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;
        }

ScheduledFutureTask本质上来说还是一个FutureTask,它也有一个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);
            }
        }

运行一次以后将任务再排进队列:

void reExecutePeriodic(RunnableScheduledFuture<?> task) {
        if (canRunInCurrentRunState(true)) {
            super.getQueue().add(task);
            if (!canRunInCurrentRunState(true) && remove(task))
                task.cancel(false);
            else
                ensurePrestart();
        }
    }

因此SFT和FutureTask比有两个不同

  1. 配置了下一次执行的时间
  2. 运行完重新塞进队列中

那么,什么时候运行呢? 那就要看队列DelayedWorkQueue怎么实现的了。

DelayedWorkQueue

  1. 首先它是一种BlockingQueue,为了兼容TPE,它被定义为BlockingQueue的实现,其实它仅仅被用来存放RunnableScheduledFutures
  2. 它的内部有一个数组用来实现一个堆(序号为x的节点的parent为(x-1)>>>1)
RunnableScheduledFuture<?>[] queue =
            new RunnableScheduledFuture<?>[INITIAL_CAPACITY];
  1. 它内部节点自己保存了一份自己的index,这样在cancel的时候加速寻找(O(n) -> O(log n)
插入一个节点时进堆时,将其提升
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);
        }
给一个节点降级
private void siftDown(int k, RunnableScheduledFuture<?> key) {
            int half = size >>> 1;
            //表示还没有到叶子上
            while (k < half) {
                int child = (k << 1) + 1;
                RunnableScheduledFuture<?> c = queue[child];
                int right = child + 1;
                //right<size,别出界。
                if (right < size && c.compareTo(queue[right]) > 0)
                    //右节点更靠前,赋给c
                    c = queue[child = right];
                //已经比子节点更靠前了,不能再往后降了
                if (key.compareTo(c) <= 0)
                    break;
                queue[k] = c;
                setIndex(c, k);
                //k是刚刚新给key的位置,后面可能还会继续降级
                k = child;
            }
            queue[k] = key;
            setIndex(key, k);
        }