ScheduledThreadPoolExecutor(JAVA线程池)

2,341 阅读17分钟

ScheduledThreadPoolExecutor前提与介绍

  • ScheduledThreadPoolExecutor是java工具并发库提供的线程池之一。
  • ScheduledThreadPoolExecutor主要提供的是线程任务延迟执行以及定时执行的服务。

前置知识

总体概括

  • ScheduledThreadPoolExecutor
    • ScheduledThreadPoolExecutor可以线程任务提供延迟执行和定时轮询的任务。
    • ScheduledThreadPoolExecutor复用了父类ThreadPoolExecutor的线程状态,工作线程调度,拒绝策略等机制,减少重复的逻辑。
    • ScheduledThreadPoolExecutor主要通过自己实现了阻塞队列DelayedWorkQueue,从而实现了延迟获取任务的效果。
    • ScheduledThreadPoolExecutor把获取到的任务进行多一层的时间控制的包装,从而实现延迟执行或定时轮询的要求。
  • ScheduledFutureTask
    • ScheduledFutureTask继承了FutureTask类,拥有其开启多线程、运行、运行重置等方法。
    • ScheduledFutureTask实现了RunnableScheduledFuture接口,拥有时间调度的功能。
    • ScheduledFutureTask本质是在原任务上增加了延迟时间和轮询时间的控制属性,使之能被延迟阻塞队列正常调度,并实现对应的延迟功能和轮询功能。
    • ScheduledFutureTask会维护一个执行时间属性,该属性是一个long类型数字,表示从某一个固定的时间点开始算起到执行时间相差的毫秒数。当前时间也可计算出对应的long值,通过比较可以判断出是否达到执行时间。
  • DelayedWorkQueue
    • DelayedWorkQueue是线程池中的任务阻塞队列,用于存放还未分配线程执行的任务。
    • DelayedWorkQueue本质是一个基于数组的最小堆,其排序的依据是根据任务的执行时间点大小排序。
    • 工作线程向DelayedWorkQueue获取任务时,若没有任务或者没有任务到达执行时间时都会返回空或者阻塞,因此获取到的任务一定是可以立刻执行的任务。
    • DelayedWorkQueue可以接收所有实现了RunnableScheduledFuture接口的实现类(该接口是ScheduledFutureTask的接口之一),但个别方法例如remove,会对RunnableScheduledFuture做出针对性的优化,提高执行效率。

ScheduledThreadPoolExecutor属性和相关方法

/**
 * 线程池关闭后是否继续执行定时任务
 */
private volatile boolean continueExistingPeriodicTasksAfterShutdown;

/**
 * 线程池关闭后是否继续执行延迟任务
 */
private volatile boolean executeExistingDelayedTasksAfterShutdown = true;

/**
 * 当任务被取消时是否要从队列中移除
 */
private volatile boolean removeOnCancel = false;

/**
 * 用于判断任务是否能够在当前状态的线程池运行。
 * 这里把任务分为两大类,定时轮询和一次性执行,两大类的任务都有对应的属性控制。
 * 
 * 核心逻辑在ScheduledThreadPoolExecutor的父类ThreadPoolExecutor实现。
 * 主要就是判断一下线程池是否是运行态,运行则返回true。
 * 若是关闭态,则根据返回对应属性的布尔值。
 * 若是其他状态则返回false。
 * 关于ThreadPoolExecutor的五大状态可以先学习ThreadPoolExecutor。
 */
boolean canRunInCurrentRunState(boolean periodic) {
    // 下面两个属性:continueExistingPeriodicTasksAfterShutdown和
    // executeExistingDelayedTasksAfterShutdown
    // 在线程池ScheduledThreadPoolExecutor统一配置。
    return isRunningOrShutdown(periodic ?
                               continueExistingPeriodicTasksAfterShutdown :
                               executeExistingDelayedTasksAfterShutdown);
}

/* 
	下面四个方法都是该线程池的构造器
     所有构造器都使用了线程池自身实现的延迟阻塞队列
*/
public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}
public ScheduledThreadPoolExecutor(int corePoolSize,
                                   ThreadFactory threadFactory) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue(), threadFactory);
}
public ScheduledThreadPoolExecutor(int corePoolSize,
                                   RejectedExecutionHandler handler) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue(), handler);
}
public ScheduledThreadPoolExecutor(int corePoolSize,
                                   ThreadFactory threadFactory,
                                   RejectedExecutionHandler handler) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue(), threadFactory, handler);
}

ScheduledThreadPoolExecutor内部类

ScheduledFutureTask 任务类

/**
 * ScheduledFutureTask是ScheduledThreadPoolExecutor执行的最小单位。
 * ScheduledFutureTask主要增加了延迟执行的相关属性,和定时执行的属性
 * ScheduledThreadPoolExecutor会把获取到的任务包装成该类,再放入阻塞队列。
 *
 * ScheduledFutureTask类实现了Delayed接口,
 * 顺序:RunnableScheduledFuture -> ScheduledFuture -> Delayed
 * 该接口定义了方法getDelay用于判断任务距离时间还差多少毫秒。
 */
private class ScheduledFutureTask<V>
    extends FutureTask<V> implements RunnableScheduledFuture<V> {

    /** 
     * 序列号
     * 该序列号由ScheduledThreadPoolExecutor的全局生成器生成。
     * 由于序列号生成器是并发安全的,因此同一个个线程池不可能存在两个相同序列号任务。
     * 序列号通常是任务创建的顺序,任务越早创建序列号越小。
     * 序列号在比较任务优先级时用到,如果两个任务都到了执行时间,
     * 则序列号越小,优先级越高。
     */
    private final long sequenceNumber;

    /**
     * 任务执行的时间戳
     * 若当前任务时间戳小于当前系统时间的时间戳时,
     * 则表示到了该任务的执行时间。
     */
    private long time;

    /**
     * 定时周期
     * 该值记录第一次任务执行完毕后,
     * 每过多少毫秒则执行任务一次。
     */
    private final long period;

    /** 
     * 实际入队的队列
     * 这里是为子类留下了一个接口,可以在当前任务中代理另一个任务
     * 当前类并没有用到该属性。
     */
    RunnableScheduledFuture<V> outerTask = this;

    /**
     * 在阻塞队列中的属性
     * ScheduledThreadPoolExecutor专门为自身实现了一个配套的阻塞队列。
     * 该阻塞队列是一个基于数组的极小堆,该值保存当前任务在堆中的索引。
     * 可以在堆中寻找当前任务时快速定位。
     */
    int heapIndex;

    /* 下面是三个构造器 */
    
    // 带延迟的任务
    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) {
        // 省略,跟上面基本一样
    }
    // 但延迟且有返回值的任务
    ScheduledFutureTask(Callable<V> callable, long ns) {
        // 省略,跟上面基本一样
    }
    
    /**
     * 当前任务的启动方法。
     * 主要分为执行延迟任务和定时任务
     * 延迟任务执行一次则任务设为完成标志。
     * 定时任务执行后并不会设为完成标志,可以被多次调用。
     */
    public void run() {
        // 是否是定时任务,主要看定时时间是否不为0
        boolean periodic = isPeriodic();
        // 判断线程池是否处于工作状态
        if (!canRunInCurrentRunState(periodic))
            cancel(false);
        else if (!periodic)
            // 若不是定时任务,则run一次。
            ScheduledFutureTask.super.run();
        else if (ScheduledFutureTask.super.runAndReset()) { // 执行并重置
            // 执行成功则设置下次执行时间
            setNextRunTime();
            // 再次判断线程池的状态是否可以执行该任务
            // 若可以则确保有工作线程在执行
            // (工作线程是ThreadPoolExecutor的概念)
            reExecutePeriodic(outerTask);
        }
    }
    
    /**
     * 取消当前任务
     * @param mayInterruptIfRunning 是否要中断当前任务
     */
    public boolean cancel(boolean mayInterruptIfRunning) {
        // 通过FutureTask父类方法,把任务取消
        boolean cancelled = super.cancel(mayInterruptIfRunning);
        // 如果取消成功,且如果取消移除标志被开启,而当前任务又在队列中
        if (cancelled && removeOnCancel && heapIndex >= 0)
            remove(this); // 从阻塞队列中移除当前任务
        return cancelled;
    }

    /**
     * 由于阻塞队列是最小堆,因此需要比较两个任务的优先级。
     * 距离执行时间近的任务排在最前面。
     * 这里ScheduledFutureTask并没有实现Comparable接口,
     * 因此该方法也不是重写方法。
     * 阻塞队列是专门为ScheduledFutureTask而定制,
     * 因此也会调用该方法进行比较。
     */ 
    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;
    }
    
    /**
     * 获取任务执行时间和当前时间的差值
     * 用于判断任务是否到了执行时间
     * 若当前时间大于任务的执行时间,则已到达。
     */
    public long getDelay(TimeUnit unit) {
        return unit.convert(time - now(), NANOSECONDS);
    }

    /**
     * 该任务是不是定时轮询任务
     */
    public boolean isPeriodic() {
        return period != 0;
    }

    /**
     * 设置下一次运行的时间
     */
    private void setNextRunTime() {
        long p = period;
        if (p > 0)
            time += p;
        else
            time = triggerTime(-p); // 对p做了限制,防止溢出
    }
}

DelayedWorkQueue 阻塞队列

  • 说明:阻塞队列只是作为线程池的一个工具,这里只展示几个核心方法,细节逻辑代码不作展示。

阻塞队列的属性

/**
 * 阻塞延迟队列,基于数组的最小堆
 * 需要取出任务时,则先判断堆顶的任务是否到达执行时间,
 * 到达则取出,否则返回null。
 */
static class DelayedWorkQueue extends AbstractQueue<Runnable>
    implements BlockingQueue<Runnable> {
    
    // 初始数组大小
    private static final int INITIAL_CAPACITY = 16; 
    // 基于数组
    private RunnableScheduledFuture<?>[] queue =
        new RunnableScheduledFuture<?>[INITIAL_CAPACITY];
    // 锁
    private final ReentrantLock lock = new ReentrantLock();
    // 元素数量
    private int size = 0;
    // 如果队列为空,该值保存第一个等待获取任务的线程
    // 若超过一个线程排队,则leader线程先取得任务,并唤醒其他线程
    // 其他线程判断是否还有任务,若还有则继续领取任务,否则再次竞争leader位。
    private Thread leader = null;
    // 锁状态
    private final Condition available = lock.newCondition();
    
    /*   ··· 下面是方法的代码,下文有核心方法的讲解。  ···   */
    
}

阻塞队列的offer方法

/**
 * 插入任务
 */
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;
}

阻塞队列的poll方法

/**
 * 取出任务,不会阻塞
 */
public RunnableScheduledFuture<?> poll() {
    final ReentrantLock lock = this.lock;
    lock.lock(); // 上锁
    try {
        RunnableScheduledFuture<?> first = queue[0];
        // 若阻塞队列没有任务,或者没有到达执行时间的任务
        if (first == null || first.getDelay(NANOSECONDS) > 0)
            return null;
        else
            return finishPoll(first); // 取出任务,并维持堆性质。
    } finally {
        lock.unlock(); // 解锁
    }
}

阻塞队列的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;
                // 判断leader位是否被占据
                if (leader != null)
                    // 若被占据则无限期堵塞,直到被唤醒
                    available.await();
                else {
                    // 运行到这表示leader位没有被占据
                    Thread thisThread = Thread.currentThread();
                    leader = thisThread;
                    try {
                        // 超时阻塞,delay是距离执行时间的毫秒数
                        available.awaitNanos(delay);
                    } finally {
                        // 运行到这表示当前线程被唤醒。
                        if (leader == thisThread)
                            leader = null;
                    }
                }
            }
        }
    } finally {
        if (leader == null && queue[0] != null)
            available.signal(); //唤醒可能被阻塞线程
        lock.unlock(); // 解锁
    }
}

阻塞队列的remove方法

/**
 * 把任务从阻塞队列移除。
 * 该方法有针对ScheduledFutureTask进行优化,加快删除的速度。
 */
public boolean remove(Object x) {
    final ReentrantLock lock = this.lock;
    lock.lock(); // 上锁
    try {
        // 获取任务x在队列中的索引。
        int i = indexOf(x);
        if (i < 0) // i< 0 表示任务不在堆中
            return false;

        // 把任务中的对索引属性设为-1
        // 只有对ScheduledFutureTask有效。
        setIndex(queue[i], -1); 
        // 下面逻辑是堆删除元素的逻辑
        // 大概就是把堆最后一个元素放到要删除的元素的位置
        // 然后从那个元素开始做siftDown操作
        int s = --size;
        RunnableScheduledFuture<?> replacement = queue[s];
        queue[s] = null;
        if (s != i) {
            siftDown(i, replacement);
            if (queue[i] == replacement)
                siftUp(i, replacement);
        }
        return true;
    } finally {
        lock.unlock(); // 解锁
    }
}

ScheduledThreadPoolExecutor核心逻辑追踪

添加任务逻辑

  • execute方法 & submit方法
    • schedule方法
      • delayedExecute方法
      • decorateTask方法

execute方法 & submit方法

ScheduledThreadPoolExecutor对外执行任务的方法有2个,分别是execute方法和submit方法。这两个方法核心都是调用

ScheduledThreadPoolExecutor的schedule方法。

public void execute(Runnable command) {
    schedule(command, 0, NANOSECONDS);
}
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);
}

schedule方法

schedule也根据传入的参数不同重写了多个对应的方法,但每个方法的逻辑基本一致,都是包装成 RunnableScheduledFuture再调用delayedExecute方法。

/**
 * 核心调度方法
 * 该方法把核心任务包装成调度任务,
 * 并加入到阻塞队列,等待被执行。
 */
public ScheduledFuture<?> schedule(Runnable command,
                                   long delay, TimeUnit unit) {
    if (command == null || unit == null)
        throw new NullPointerException();
    // 这里先对本身任务Runable包装成ScheduledFutureTask类,
    // 从而增加了对时间调度的管理信息。
    // decorateTask是ScheduledThreadPoolExecutor保留的二级包装方法。
    RunnableScheduledFuture<?> t = decorateTask(command,
    	new ScheduledFutureTask<Void>(command, null, triggerTime(delay, unit)));
    delayedExecute(t); // 延迟执行
    return t;
}

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
            // 该方法在ThreadPoolExecutor实现
            // 主要功能是确保有工作线程轮询阻塞队列。
            ensurePrestart();
    }
}

decorateTask方法

/**
 * 这个接口是ScheduledThreadPoolExecutor的保留方法。
 * 它允许子类对RunnableScheduledFuture及任务本身做额外的处理或包装。
 */
protected <V> RunnableScheduledFuture<V> decorateTask(
    Runnable runnable, RunnableScheduledFuture<V> task) {
    return task;
}

核心调度逻辑

  • 前面回顾整梳理
  • reExecutePeriodic方法
    • ensurePrestart方法

前面回顾整梳理

整理一下外部调用execute方法后,内部的调用流程:

  1. 外部调用ScheduledThreadPoolExecutor的execute方法,传入Runable任务。
  2. ScheduledThreadPoolExecutor直接把Runable交给了schedule方法。
  3. schedule方法把Runable任务包装成ScheduledFutureTask类,即调度任务,该类增加了延迟执行的时间和定时执行的时间。
  4. schedule方法把调度任务交给了delayedExecute方法。
  5. delayedExecute把调度任务放入到延迟阻塞队列,并调用了ensurePrestart方法。
  6. ensurePrestart会激活工作线程从队列获取任务。
  7. 工作线程获取到任务后会调用任务的run方法,即执行调度任务。
  8. 调度任务执行run时判断当前任务是定时轮询任务还是一次性任务
    1. 若是一次性任务则直接执行 FutureTask 的run方法,并把任务置为完成状态。
    2. 若是定时轮询任务则执行 FutureTask 的runAndReset方法,重新计算执行时间,并调用ScheduledThreadPoolExecutor 的reExecutePeriodic 方法进入下一次轮询的等待。

reExecutePeriodic方法

/**
 * 重置定时任务
 * 该方法把执行过后的定时轮询任务重新加入阻塞队列
 */
void reExecutePeriodic(RunnableScheduledFuture<?> task) {
    // 判断当前任务是否可以运行
    if (canRunInCurrentRunState(true)) { 
        super.getQueue().add(task);
        if (!canRunInCurrentRunState(true) && remove(task))
            task.cancel(false);
        else
            ensurePrestart();
    }
}

ensurePrestart方法

/**
 * 该方法确保有工作线程在运行
 * 该方法是ThreadPoolExecutor的方法,
 * 但在ThreadPoolExecutor中并没有被使用到。
 */
void ensurePrestart() {
    int wc = workerCountOf(ctl.get());
    // 工作线程数小于核心线程数时
    if (wc < corePoolSize)
        // 添加一个核心工作线程
        addWorker(null, true);
    // 若核心线程数为0,且没有工作线程
    else if (wc == 0)
        // 则新建一个工作线程
        addWorker(null, false);
}

关闭线程池逻辑

  • shutdown方法
    • onShutdown方法
  • shutdownNow方法

shutdown方法

/**
 * 关闭线程池
 */
public void shutdown() {
    // 调用父类ThreadPoolExecutor的关闭线程池方法
    super.shutdown(); 
}

/**
 * 这是ThreadPoolExecutor的shutdown方法。
 * 这里引用自ThreadPoolExecutor的教程
 * 详细信息可以先学习ThreadPoolExecutor。
 *
 * ThreadPoolExecutor外部通过调用该方法关闭线程池
 * 该方法被执行后,线程池状态被设为SHUTDOWN,
 * 该状态下线程池不再接收新的任务,但会继续执行队列中的任务,
 * 直到全部执行完毕。
 */
public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();  // 确定有关闭线程的权限
        advanceRunState(SHUTDOWN); // 设为关闭状态
        interruptIdleWorkers(); // 中断所有空闲工作线程
        onShutdown(); // 空方法,该方法被当前线程池重写了
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
}

onShutdown方法

/**
 * 根据线程池策略清空一次性任务和定时轮询任务。
 */ 
@Override 
void onShutdown() {
    BlockingQueue<Runnable> q = super.getQueue();
    // 获取策略
    boolean keepDelayed =
        getExecuteExistingDelayedTasksAfterShutdownPolicy();
    boolean keepPeriodic =
        getContinueExistingPeriodicTasksAfterShutdownPolicy();
    if (!keepDelayed && !keepPeriodic) {
        // 若线程池关闭后,不执行任何的任务
        // 则轮询标离阻塞队列的所有任务,并全部取消
        // 最后清空阻塞队列。
        for (Object e : q.toArray())
            if (e instanceof RunnableScheduledFuture<?>)
                ((RunnableScheduledFuture<?>) e).cancel(false);
        q.clear();
    }
    else {
        // 否则按照策略删除任务
        for (Object e : q.toArray()) {
            if (e instanceof RunnableScheduledFuture) {
                RunnableScheduledFuture<?> t =
                    (RunnableScheduledFuture<?>)e;
                // 若任务按照策略不被保留
                // 或者任务已经被取消
                if ((t.isPeriodic() ? !keepPeriodic : !keepDelayed) ||
                    t.isCancelled()) { 
                    // 从队列移除,并取消任务。
                    // 任务被多次取消不会抛异常,不影响逻辑执行。
                    if (q.remove(t))
                        t.cancel(false);
                }
            }
        }
    }
    // 尝试终止线程池
    // 用于维护ThreadPoolExecutor的逻辑
    // 只有所有任务都被删除线程池才会终止。
    tryTerminate(); 
}

shutdownNow方法

/**
 * 立刻停止线程池
 */
public List<Runnable> shutdownNow() {
    return super.shutdownNow();
}

/**
 * 这是ThreadPoolExecutor的shutdownNow方法。
 * 这里引用自ThreadPoolExecutor的教程
 * 详细信息可以先学习ThreadPoolExecutor。
 *
 * 立刻关闭线程池
 * 把线程池状态设为STOP
 * 所有工作线程无论是否活跃都会被中断。
 */
public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(STOP); // 设为STOP状态
        interruptWorkers(); // 中断活跃状态下工作线程
        tasks = drainQueue(); // 获取剩余未执行的任务
    } finally {
        mainLock.unlock();
    }
    // 终止线程池
    // STOP状态下会尝试终止空闲线程
    // 若所有工作线程都为空,则把线程池转化为终止态。
    tryTerminate(); 
    return tasks; // 返回没有执行的任务列表
}

Executors工厂类

ScheduledExecutorService 抽象调度服务

/**
 * 抽象调度服务(门面模式)
 * 该服务是Executors为所有调度类型的线程池抽象出来的服务接口,
 * 从而可以为所有调度类型的线程池提供已给统一的接口,屏蔽不同线程池的差异。
 */
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);
}

DelegatedScheduledExecutorService调度服务代理类

/**
 * ScheduledExecutorService实现代理类。
 * 套上这层代理类可以阻止外部程序修改线程池的配置。
 */
static class DelegatedScheduledExecutorService
    extends DelegatedExecutorService
    implements ScheduledExecutorService {
    private final ScheduledExecutorService e;
    DelegatedScheduledExecutorService(ScheduledExecutorService executor) {
        super(executor);
        e = executor;
    }
    /*
    	观察该代理类实现的接口和构造器传入的参数类型,
    	容易发现传入进来的实体都实现了本类需要实现的方法。
    	而本类的确也是一个代理类,因此所有要实现的方法都是通过executor对应的方法实现。
    	不过本类也继承了DelegatedExecutorService,因此也获得了它所有的方法。
    	
    	为了简洁,不再展示对应的方法。
    */
}

调度线程池

/**
 * 生成一个调度线程池,指定核心线程的数量。
 */
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}
/**
 * 在上面基础上指定了对应的线程工厂。
 */
public static ScheduledExecutorService newScheduledThreadPool(
    int corePoolSize, ThreadFactory threadFactory) {
    return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}

调度串行线程池

/**
 * 生成只有一个核心线程的调度线程池
 * Executors还在ScheduledThreadPoolExecutor外部套上了一个代理类
 * 使外部程序无论如何也无法修改线程池的配置。
 */
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
    return new DelegatedScheduledExecutorService
        (new ScheduledThreadPoolExecutor(1));
}
public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) {
    return new DelegatedScheduledExecutorService
        (new ScheduledThreadPoolExecutor(1, threadFactory));
}

总结

  • ScheduledThreadPoolExecutor通过ScheduledFutureTask 实现了对延迟时间的记录,通过DelayedWorkQueue 实现了对延迟时间的控制。
  • ScheduledThreadPoolExecutor可以控制线程池关闭后轮询任务和延迟任务是否继续执行。
  • DelayedWorkQueue 基于堆,增删查改时都会全局上锁,且队列长度理论上无限。

核心线程数不应当为零

  • 原理:由于延迟队列的机制,即使任务队列存在任务都有可能返回空值。而ScheduledThreadPoolExecutor所依赖的ThreadPoolExecutor机制中,若线程不属于核心线程则在获取不到任务后会被释放。线程池在释放线程后如果发现工作线程数为0,而阻塞队列不为空,就会重新创建一个新的工作线程,而新的工作线程又会尝试去获取新任务...
  • 假设:如果核心线程数设为0。
  • 结果:因为核心线程数为0,则所有工作线程都不是核心线程,获取不到任务都会被释放。这时最后一个工作线程释放后,线程池会判断阻塞队列是否还有任务,如果有会立刻新建一个工作线程,但该线程获取任务时又为空,再次被释放,而线程池又会创建一个新的线程...如次一直循环,直到阻塞队列为空为止。而这个循环不断创建和销毁线程,开销及其庞大,应当被避免。