分析Executor家族成员
前奏
Executor是家族中的老大,其下还有ExecutorService、Executors、ThreadPoolExecutor、ScheduledThreadPoolExecutor和ForkJoinPool,对于ForkJoinPool是在JDK1.7中新增的,其中可能还涉及到诸多理论,故而打算会另起文章进行说明。不管是在工作还是面试中,难免会耳闻线程池,简单说下个人对线程池的理解,后续将对Executor的家族成员做一一介绍,让我们开始旅程吧,此次探索基于JDK1.8。
线程池:将创建好的线程放到一个池中进行使用、调度及管理,等到任务一提交后就可以直接运行了,省去了创建线程的时间,同时使用者不必考虑如何管理线程,让使用者更加专注于任务。线程池中会涉及到核心线程数(corePoolSize)、最大线程数(maximumPoolSize)、队列,假设线程个数小于核心线程数,对于新提交的任务会直接创建新线程,当线程个数超过核心线程数且队列未满时,将新任务放入到队列中,等到空闲线程后再去执行,万一要是队列满了且线程数不超过最大线程数时,则会创建新线程执行任务,哪天要是大于最大线程数,则任务将被拒绝,对于拒绝可指定不同的策略,后续将会详细介绍。
家族成员

每个成员的地位很明确了,我们从Executors开始入手。
Executors
Executors是其家族成员的工厂类,是个便利的工具,使用常用的配置调用方法来创建ExecutorService、ScheduledExecutorService、ThreadFactory对象,工作中经常使用此方式来完成作业。
简单方法
/**
* 无法构造该对象实例
*/
public class Executors {
/**
* 无法构造该对象
*/
private Executors() {}
/**
* 创建具有固定线程数的线程池
* 该线程池采用的队列是无界队列,无界队列很容易理解,没有边界、没有上限、无限制添加,在我们熟悉的数据结构当中,内部采用的应该是链条式结构
* 线程个数小于核心线程数时,对于新提交的任务直接创建新线程执行,当线程个数超过核心线程数时,对于新提交的任务就被放入到队列当中了
* 恰好是个无界队列,也就是说万一要是没有空闲的线程的话,那此队列中的任务数量将会无限增大,或者说当任务的提交速速远大于任务的处理速度时,队列中的任务数量也会无限增大
* 这也最终导致了线程池中最多只会有核心线程数的线程,最大线程数没啥用了,线程池中的线程在创建后即使空闲下来了也不会被关闭,除非主动调用shutdown方法
*
*
* 使用场景:
* 由于任务有可能出现队列中进行等待,故而执行中的任务与等待中的任务不能有依赖关系,该种方式能够消除短暂的大量任务提交
*
* @param nThreads 核心线程数/最大线程数
* @return 线程池对象
*/
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}
////////// ForkJoinPool将会新起文章进行讲解 /////////
/**
* 创建具有固定线程数的线程池,线程由使用者自定义创建
* 默认情况下,由Executors#defaultThreadFactory来帮助使用者创建线程,所有的线程都位于同一个线程组中,并且拥有相同的优先级和非守护线程
*
* 使用场景:
* 1. 由于任务有可能出现队列中进行等待,故而执行中的任务与等待中的任务不能有依赖关系,该种方式能够消除短暂的大量任务提交
* 2. 通过指定线程的工厂类来自定义线程的创建过程,可自定义每个线程的具有不同的优先级、线程组、线程的名称
*
* @param nThreads 核心线程数/最大线程数
* @param threadFactory 线程的工厂类
* @return 线程池对象
*/
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory);
}
/**
* 创建具有单个线程的线程池,也就是池中只有一个线程
* 该线程池采用的无界队列,很可惜,对于多任务来说,只能有一个任务在执行,其他的任务都需要等待,这也保证了任务按顺序执行
* 线程池中的线程在创建后即使空闲下来了也不会被关闭,除非主动调用shutdown方法
* newSingleThreadExecutor与newFixedThreadPool(1)的区别:对于newFixedThreadPool方法来说,它是可以调用指定方法来重新配置线程的个数,而对于newSingleThreadExecutor来说,它的实现类并未拥有这种类型的方法,所以说它是不可配置* 的,也就是真正意义上的single
*
* 使用场景:
* 1. 由于任务有可能出现队列中进行等待,故而执行中的任务与等待中的任务不能有依赖关系,该种方式能够消除短暂的大量任务提交
* 2. 可用于执行优先级较低的任务
*
* @return 线程池对象
*/
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));
}
/**
* 创建具有单个线程的线程池,线程由使用者自定义创建
* 默认情况下,由Executors#defaultThreadFactory来帮助使用者创建线程,所有的线程都位于同一个线程组中,并且拥有相同的优先级和非守护线程
* 线程池中的线程在创建后即使空闲下来了也不会被关闭,除非主动调用shutdown方法
*
* 使用场景:
* 1. 由于任务有可能出现队列中进行等待,故而执行中的任务与等待中的任务不能有依赖关系,该种方式能够消除短暂的大量任务提交
* 2. 可用于执行优先级较低的任务
* 3. 通过指定线程的工厂类来自定义线程的创建过程,可自定义每个线程的具有不同的优先级、线程组、线程的名称
*
* @param threadFactory 线程的工厂类
*/
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory));
}
/**
* 创建无限制线程个数的线程池
* 该线程池采用的是直接交接,简单理解就是提交的任务被直接交给线程,如果没有空闲的线程来运行任务的话,那么就创建一个新线程来执行它
* 由于最大线程数是无限制,所以当任务的提交速速远大于任务的处理速速时,将会造成线程的无限增长
* 当任务被处理完后,当前的线程就处于空闲状态,当空闲时间超过60s后该线程将被回收,若中途有其他任务则重复使用该线程,因此即使线程池长时间不使用的话也不会造成资源的浪费,因为线程都被回收了
*
* 使用场景:
* 1. 由于没有等待队列,所以可以用来处理任务之间具有依赖关系的情况
* 2. 处理大量小型异步任务
*
* @return 线程池对象
*/
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
}
/**
* 创建无限制线程个数的线程池,线程由使用者自定义创建
* 默认情况下,由Executors#defaultThreadFactory来帮助使用者创建线程,所有的线程都位于同一个线程组中,并且拥有相同的优先级和非守护线程
* 线程池中的线程空闲超过60s后将被回收
*
* 使用场景:
* 1. 由于没有等待队列,所以可以用来处理任务之间具有依赖关系的情况
* 2. 处理大量小型任务
* 3. 通过指定线程的工厂类来自定义线程的创建过程,可自定义每个线程的具有不同的优先级、线程组、线程的名称
*
* @param threadFactory 线程的工厂类
* @return 线程池对象
*/
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), threadFactory);
}
/**
* 创建具有单个定时线程的线程池
* 定时线程:在指定的延迟后或周期性运行
* 该线程池采用的延迟队列,内部采用数组来存储任务,采用Condition进行延迟操作,对于多任务来说,只能有一个任务在执行,其他的任务都需要等待,这也保证了任务按顺序执行
* 线程池中的线程在创建后即使空闲下来了也不会被关闭,除非主动调用shutdown方法
*
* 使用场景:
* 1. 延迟或周期性的优先级较低的任务
*
* @return 线程池对象
*/
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService (new ScheduledThreadPoolExecutor(1));
}
/**
* 创建具有单个定时线程的线程池
* 定时线程:在指定的延迟后或周期性运行
* 默认情况下,由Executors#defaultThreadFactory来帮助使用者创建线程,所有的线程都位于同一个线程组中,并且拥有相同的优先级和非守护线程
*
* 使用场景:
* 1. 延迟或周期性的优先级较低的任务
* 2. 通过指定线程的工厂类来自定义线程的创建过程,可自定义每个线程的具有不同的优先级、线程组、线程的名称
*
* @param threadFactory 线程的工厂类
* @return 线程池对象
*/
public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) {
return new DelegatedScheduledExecutorService (new ScheduledThreadPoolExecutor(1, threadFactory));
}
/**
* 创建具有固定定时线程数的线程池
* 定时线程:在指定的延迟后或周期性运行
* 若corePoolSize > 1, 对于多任务来说,可以有多个任务同时执行,不会按照顺序执行
* 线程池中的线程在创建后即使空闲下来了也不会被关闭,除非主动调用shutdown方法
*
* 使用场景:
* 1. 延迟或周期性的多任务
*
* @param corePoolSize 核心线程数
* @return 线程池对象
*/
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
/**
* 创建具有固定定时线程数的线程池
* 定时线程:在指定的延迟后或周期性运行
* 若corePoolSize > 1, 对于多任务来说,可以有多个任务同时执行,不会按照顺序执行
* 默认情况下,由Executors#defaultThreadFactory来帮助使用者创建线程,所有的线程都位于同一个线程组中,并且拥有相同的优先级和非守护线程
*
* 使用场景:
* 1. 延迟或周期性的多任务
* 2. 通过指定线程的工厂类来自定义线程的创建过程,可自定义每个线程的具有不同的优先级、线程组、线程的名称
*
* @param corePoolSize 核心线程数
* @param threadFactory 线程的工厂类
* @return 线程池对象
*/
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory) {
return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
/**
* 自定义线程池,窄化了线程池中的方法,也就是说线程池中的一些方法是不可调用的
* @param executor 自定义线程池
* @return 窄化后的线程池对象
*/
public static ExecutorService unconfigurableExecutorService(ExecutorService executor) {
if (executor == null)
throw new NullPointerException();
return new DelegatedExecutorService(executor);
}
/**
* 自定义线程池(定时线程),窄化了线程池中的方法,也就是说线程池中的一些方法是不可调用的
* @param executor 自定义线程池
* @return 窄化后的线程池对象
*/
public static ScheduledExecutorService unconfigurableScheduledExecutorService(ScheduledExecutorService executor) {
if (executor == null)
throw new NullPointerException();
return new DelegatedScheduledExecutorService(executor);
}
/**
* 默认的线程工厂类,用于创建线程
* 创建的线程将属于同一个线程组,拥有同样的优先级5和非守护线程,线程的名称:pool-poolNumberSequence-thread-threadNumberSequence
* @return 默认的线程工厂类
*/
public static ThreadFactory defaultThreadFactory() {
return new DefaultThreadFactory();
}
/**
* 默认的线程工厂类,区别在于具备了与当前调用线程相同的权限,并且为每个线程设置了类加载器与访问控制上下文
* @return 默认的线程工厂类
*/
public static ThreadFactory privilegedThreadFactory() {
return new PrivilegedThreadFactory();
}
/**
* 返回Callable对象
* RunnableAdapter#call只是单纯的调用run方法,并未启动线程
* Callable与Runnable相比,可以有返回值
* 不过即使run方法中抛出异常,该方法也捕捉不到,毕竟run方法无法抛出
* @param task 任务
* @param result 结果
* @return Callable对象
*/
public static <T> Callable<T> callable(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter<T>(task, result);
}
/**
* 返回Callable对象
* RunnableAdapter#call只是单纯的调用run方法,并未启动线程
* Callable与Runnable相比,可以有返回值
* 不过即使run方法中抛出异常,该方法也捕捉不到,毕竟run方法无法抛出
* @param task 任务
* @return Callable对象
*/
public static Callable<Object> callable(Runnable task) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter<Object>(task, null);
}
/**
* 返回Callable对象
* Callable与Runnable相比,可以有返回值
* PrivilegedAction#run方法也是无法抛出异常,故该方法也是捕捉不到异常信息
* @param action 任务
* @return Callable对象
*/
public static Callable<Object> callable(final PrivilegedAction<?> action) {
if (action == null)
throw new NullPointerException();
return new Callable<Object>() {
public Object call() {
return action.run();
}
};
}
/**
* 返回Callable对象
* Callable与Runnable相比,可以有返回值
* PrivilegedExceptionAction#run方法可以抛出异常,故而该方法可以捕捉异常信息
* @param action 任务
* @return Callable对象
*/
public static Callable<Object> callable(final PrivilegedExceptionAction<?> action) {
if (action == null)
throw new NullPointerException();
return new Callable<Object>() {
public Object call() throws Exception {
return action.run();
}
};
}
/**
* 返回Callable对象,在特权环境下执行
* @param callable Callable对象
* @return Callable对象
*/
public static <T> Callable<T> privilegedCallable(Callable<T> callable) {
if (callable == null)
throw new NullPointerException();
return new PrivilegedCallable<T>(callable);
}
/**
* 返回Callable对象,在特权环境下执行,拥有当前线程的访问控制权限与类加载器
* @param callable Callable对象
* @return Callable对象
*/
public static <T> Callable<T> privilegedCallableUsingCurrentClassLoader(Callable<T> callable) {
if (callable == null)
throw new NullPointerException();
return new PrivilegedCallableUsingCurrentClassLoader<T>(callable);
}
/**
* 自定义Callable对象
*/
static final class RunnableAdapter<T> implements Callable<T> {
//任务
final Runnable task;
//调用结果
final T result;
RunnableAdapter(Runnable task, T result) {
this.task = task;
this.result = result;
}
public T call() {
task.run(); //只是单纯的调用run方法,并未开始线程
return result;
}
}
///////其他类似的Callable对象就不做展示了///////
/**
* 默认的线程工厂类,用于创建新线程
*/
static class DefaultThreadFactory implements ThreadFactory {
//自定义序列号,相同的数字表示同一个线程池对象
private static final AtomicInteger poolNumber = new AtomicInteger(1);
//线程组
private final ThreadGroup group;
//自定义序列号,数字表示创建了多少个线程
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-";
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0); //创建的线程都属于同一个线程组,线程的名称根据线程的个数来区别
if (t.isDaemon())
t.setDaemon(false); //创建的线程都属于非守护线程
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY); //创建的线程的优先级都是5
return t;
}
}
////////其他ExecutorService也不做展示了////////
}
总结
-
newFixedThreadPool:创建具体
固定线程数的线程池,内部采用无界队列。当线程个数小于核心线程数时,对于新提交的任务直接创建线程执行,当线程个数大于核心线程数时,对于新提交的任务就被放入到队列中。由于是个无界队列,也就是说当没有空闲的线程时,随着任务的不断提交队列将无限增大,或者说当任务的提交速度远大于任务的处理速度时,队列也会无限增大。正如方法名一样,线程池中的线程个数最多只有核心线程数,最大线程数没有起到作用,线程池中的线程即使空闲了也不会被关闭,除非主动调用shutdown方法。该方法还可以指定自定义线程工厂类,由使用者自己去决定如何创建线程,以及线程的名称、优先级、县线程组等。 -
newSingleThreadExecutor:创建具有
单个线程的线程池,也就是说池中只有一个线程供使用,内部同样采用无界队列。由于只有一个线程处于活跃状态,只能有一个任务在执行,其余的任务只能在队列中等待着,这保证了它可以按顺序执行任务。线程池中的线程即使空闲了也不会被关闭,除非主动调用shutdown方法,该方法同样可以指定自定义线程工厂类。该方法与newFixedThreadPool(1)(简称nft)的区别在于,nft可以调用指定方法来重新配置核心线程数的值,而对于该方法而言,它的实现类并未拥有这种功能的方法,所有它是不可配置的,真正意义上的single。 -
newCachedThreadPool:创建
无限制线程个数的线程池,内部采用直接交接的形式,很容易理解,来一个新任务我直接用取一个线程取执行,不管是复用还是新创建的线程,反正是不用等。由于线程个数是无限制的,也就是说当没有可以复用的空闲线程时,随着任务的不断提交线程池中的线程将无限增长。线程池中的线程一旦空闲下来的时间超过指定的保活时间(默认60s)将被回收,若中途有提交新任务则复用线程,因此线程池中的线程即使长时间不使用也不会造成资源的浪费,都被回收了。该方法同样可以指定自定义线程工厂类。 -
newSingleThreadScheduledExecutor:创建具有
单个定时线程的线程池,所谓的定时线程是指延迟或周期性执行,内部采用延迟队列,该队列内部使用数组来存储任务,使用Condition进行延迟操作。由于只有一个线程处于活跃状态,只能有一个任务在执行,其余的任务只能在队列中等待着,这保证了它可以按顺序执行任务。线程池中的线程即使空闲了也不会被关闭,除非主动调用shutdown方法。该方法同样可以指定自定义线程工厂类。 -
newScheduledThreadPool:创建具有
固定定时线程数的线程池,同样采用延迟队列。若核心线程数大于1,对于多任务来说可以同时运行多个。线程池中的线程即使空闲了也不会被关闭,除非主动调用shutdown方法。该方法同样可以指定自定义线程工厂类。 -
每个方法的使用场景可看具体方法。
ThreadPoolExecutor
ThreadPoolExecutor是家族中的核心成员,担负了家族中的重要工作,简单来说底层就是使用该类对线程进行创建与管理,以及执行任务,内部维护了线程池的各种状态。
-
运行中(RUNNING):接收新任务并处理队列中的任务。
-
关闭(SHUTDOWN):不接收新任务,而是处理队列中的任务。
-
停止(STOP):不接受新任务,不处理队列中的任务及中断进行中的任务。
-
终止中(TIDYING):所有任务都已终止,工作线程为0。
-
已终止(TERMINATED):terminated方法执行完成。

以上每个状态的中文解释可能不太准确,更多的是想帮助大家去理解,若觉得不适应或者晦涩难懂可以忽略,接着介绍下线程池的拒绝任务策略。
-
ThreadPoolExecutor.AbortPolicy:当线程池拒绝任务时,采用直接抛出RejectedExecutionException异常。
-
ThreadPoolExecutor.CallerRunsPolicy:当线程池拒绝任务时,采用只要线程池未关闭,由调用execute的线程运行任务,这提供了一种反馈机制,而若线程池已关闭,则任务将被丢弃。
-
ThreadPoolExecutor.DiscardPolicy:当线程池拒绝任务时,采用直接丢弃了任务,相当于直接不执行任务了,很干脆。
-
ThreaPollExecutor.DiscardOldestPolicy:当线程池拒绝任务时,采用只要线程池未关闭,则将丢弃队列中头部位置的任务(最早入队列的任务),然后重复执行指定任务(调用execute方法,有可能再次被拒绝导致又重复执行)。
数据结构
public class ThreadPoolExecutor extends AbstractExecutorService {
/**
* ctl包装了工作线程的数量(workerCount)与线程池的状态(runState),也就是说通过该变量既可以获取到工作线程的数量,也可以获取到线程池的状态,有点厉害啊!
* 其中将工作线程的数量限制到 2^29 -1,简单来说,一个32位的int类型来说,后29位表示工作线程的数量,剩余3位表示线程池的状态
*/
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
/**
* 通过移位区分工作线程的数量与线程池的状态
*/
private static final int COUNT_BITS = Integer.SIZE - 3;
/**
* 表示工作线程的数量的最大值
*/
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
/**
* TERMINATED > TIDYING > STOP > SHUTDOWN > RUNNING
* 由于工作线程取的是后29位,所以我们可以知道:TIDYING > STOP + CAPACITY(这两者就差1)
* 所以我们更能得出:TERMINATED > TIDYING + CAPACITY > TIDYING > STOP + CAPACITY > STOP > SHUTDOWN + CAPACITY > SHUTDOWN > RUNNING + CAPACITY > RUNNING
* 而我们知道ctl代表着工作线程 + 线程池的状态,故而简化为:TERMINATED > ctl(TIDYING) > TIDYING > ctl(STOP) > STOP > ctl(SHUTDOWN) > SHUTDOWN > ctl(RUNNING) > RUNNING
* 所以我们要想知道线程池处于哪个状态区间,则可以直接比较ctl与状态值,比如ctl > STOP,则说明线程池处于TERMINATED或TIDYING
* 这样子ctl就不用为了获取状态值而进行拆解,为什么要分析这东西呢,因为后面有方法涉及到
*/
/**
* 线程池处于运行中状态
*/
private static final int RUNNING = -1 << COUNT_BITS;
/**
* 线程池处于关闭状态
*/
private static final int SHUTDOWN = 0 << COUNT_BITS;
/**
* 线程池处于停止状态
*/
private static final int STOP = 1 << COUNT_BITS;
/**
* 线程池处于终止中状态
*/
private static final int TIDYING = 2 << COUNT_BITS;
/**
* 线程池处于已终止状态
*/
private static final int TERMINATED = 3 << COUNT_BITS;
/**
* 存储任务,工作线程从队列中获取任务并处理
*/
private final BlockingQueue<Runnable> workQueue;
/**
* 目前只需要知道是锁就可以了,至于什么类型的锁以后会新起文章进行详细介绍
*/
private final ReentrantLock mainLock = new ReentrantLock();
/**
* 存储线程池中的所有工作线程
*/
private final HashSet<Worker> workers = new HashSet<Worker>();
/**
* 条件等待,与Lock组合使用,用于代替Synchronized与对象锁,以后也会新起文章进行讲解
*/
private final Condition termination = mainLock.newCondition();
/**
* 线程池的最大线程数,简单来说就是线程池中有多少个线程
*/
private int largestPoolSize;
/**
* 已完成任务的数量,该变量只会在工作线程结束后进行更新
*/
private long completedTaskCount;
/**
* 线程的工厂类,用于创建线程
*/
private volatile ThreadFactory threadFactory;
/**
* 拒绝任务的策略,当线程池达到最大线程数或关闭/停止状态时就会拒绝任务的提交
*/
private volatile RejectedExecutionHandler handler;
/**
* 当线程池中的线程个数大于核心线程数时,其空闲线程的存活时间,超过该时间后将被回收,若线程个数小于核心线程数则不会在回收空闲线程
* 或者设置了allowCoreThreadTimeOut = true,表示所有线程的空闲时间超过指定时间后将被回收
* 执行任务中的线程不算空闲线程,等待任务的线程是空闲线程,简单来说,空闲线程就是没事做
*
* 若未设置存活时间,即默认是0,表示永远不会回收线程,即使线程个数超过了核心线程数
*/
private volatile long keepAliveTime;
/**
* 默认情况下是false,表示核心线程即使空闲了也不会被回收
* 若为true,表示所有线程的空闲时间超过keepAliveTime后将被回收
*/
private volatile boolean allowCoreThreadTimeOut;
/**
* 核心线程数
* allowCoreThreadTimeOut = false,最终可以保留核心线程数的工作线程
* allowCoreThreadTimeOut = true,最终工作线程将为0
*/
private volatile int corePoolSize;
/**
* 最大线程数
* 该数值受CAPACITY限制,也就是说最大是CAPACITY
*/
private volatile int maximumPoolSize;
/**
* 拒绝任务的默认策略
*/
private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();
}
构造方法
/**
* 初始化,采用默认拒绝任务的策略、默认的线程工厂类
* @param corePoolSize 核心线程数
* @param maximumPoolSize 最大线程数
* @param keepAliveTime 保活时间
* @param unit 时间单位
* @param workQueue 队列
*/
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler);
}
/**
* 初始化,采用默认拒绝任务的策略、自定义线程工厂类
* @param corePoolSize 核心线程数
* @param maximumPoolSize 最大线程数
* @param keepAliveTime 保活时间
* @param unit 时间单位
* @param workQueue 队列
* @param threadFactory 线程的工厂类
*/
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, defaultHandler);
}
/**
* 初始化,采用默认的线程工厂类
* @param corePoolSize 核心线程数
* @param maximumPoolSize 最大线程数
* @param keepAliveTime 保活时间
* @param unit 时间单位
* @param workQueue 队列
* @param handler 拒绝任务的策略
*/
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), handler);
}
/**
* 初始化
* @param corePoolSize 核心线程数
* @param maximumPoolSize 最大线程数
* @param keepAliveTime 保活时间
* @param unit 时间单位
* @param workQueue 队列
* @param threadFactory 线程的工厂类
* @param handler 拒绝任务的策略
*/
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ? null : AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime); //将时间转换成纳秒
this.threadFactory = threadFactory;
this.handler = handler;
}
简单方法
/**
* 通过ctl原子变量获取线程池的状态
* ~CAPACITY:对于取反运算符,只要先获取到CAPACITY的补码后对其进行取反(0变成1,1变成0)即可获取到结果
* @param ctl
* @return 结果值
*/
private static int runStateOf(int c) {
return c & ~CAPACITY;
}
/**
* 通过ctl原子变量获取工作线程数
* @param c ctl
* @return 结果值
*/
private static int workerCountOf(int c) {
return c & CAPACITY;
}
/**
* 更新ctl原子变量的值,即当线程池的状态或工作线程的数量发生变化时就会调用该方法
* 在更新完ctl的值后就可以调用runStateOf方法获取线程池的状态,或调用workerCountOf方法获取工作线程的数量
* @param rs 线程池的状态
* @param wc 工作线程数
* @return 结果值
*/
private static int ctlOf(int rs, int wc) {
return rs | wc;
}
/**
* 工作线程,包装了提交的任务与线程
* 执行路径:thread -> worker -> task
* 该类实现了AQS,虽然对AQS还不了解,但并不影响我们看源码
* 该类中定义了三种状态:-1表示工作线程还未启动 0表示工作线程未上锁 1表示工作线程上锁
*/
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
private static final long serialVersionUID = 6138294804551838833L;
//新创建的线程
final Thread thread;
//任务
Runnable firstTask;
//当前工作线程完成的任务数量
volatile long completedTasks;
/**
* 初始化工作线程
* 此时工作线程的状态为-1,表示还未启动
* 每一个新的工作线程会创建新线程
* @param firstTask 任务
*/
Worker(Runnable firstTask) {
setState(-1); //该状态会在工作线程启动后将其变成1
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this); //创建新线程
}
/**
* 启动工作线程
*/
public void run() {
runWorker(this);
}
/**
* 当前工作线程是否上锁
* @return 工作线程是否上锁
*/
protected boolean isHeldExclusively() {
return getState() != 0;
}
/**
* 尝试获取锁
* @param unused 未使用到
* @return 是否获取到锁
*/
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
/**
* 尝试释放锁
* @param unused 未使用到
* @return 是否释放锁
*/
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
/**
* 获取锁,一定会获取到锁,只不过可能需要阻塞等待,更多的知识以后会新起文章进行深入分析
*/
public void lock() {
acquire(1);
}
/**
* 尝试获取锁,获取不到就直接返回了,不会阻塞等待
* @return 是否获取到锁
*/
public boolean tryLock() {
return tryAcquire(1);
}
/**
* 释放锁
*/
public void unlock() {
release(1);
}
/**
* 当前工作线程是否上锁
* @return 工作线程是否上锁
*/
public boolean isLocked() {
return isHeldExclusively();
}
/**
* 中断已启动的工作线程
*/
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
/**
* 比较当前线程池的状态是否小于指定状态值
* 简单来说就是为了知道线程池处于哪个状态区间
* 更为具体的分析可看最上面
* @param c ctl
* @param s 指定状态值
* @return 结果值
*/
private static boolean runStateLessThan(int c, int s) {
return c < s;
}
/**
* 比较当前线程池的状态是否大于指定状态值
* 简单来说就是为了知道线程池处于哪个状态区间
* @param c ctl
* @param s 指定状态值
* @return 结果值
*/
private static boolean runStateAtLeast(int c, int s) {
return c >= s;
}
/**
* 当前线程池是否处于运行中状态(RUNNING)
* @param c ctl
* @return 结果值
*/
private static boolean isRunning(int c) {
return c < SHUTDOWN;
}
/**
* 以原子化的方式增加工作线程的数量,这里利用CAS,以后也会新起文章进行讲解
* @param expect 预期值
* @return 结果值,false表示实际值与预期值不同导致更新值失败
*/
private boolean compareAndIncrementWorkerCount(int expect) {
return ctl.compareAndSet(expect, expect + 1);
}
/**
* 以原子化的方式减少工作线程的数量
* @param expect 预期值
* @return 结果值,false表示实际值与预期值不同导致更新值失败
*/
private boolean compareAndDecrementWorkerCount(int expect) {
return ctl.compareAndSet(expect, expect - 1);
}
/**
* 同上
*/
private void decrementWorkerCount() {
do {
} while (! compareAndDecrementWorkerCount(ctl.get()));
}
/**
* 关闭或停止线程池时会调用此方法
* 若线程池的当前状态大于指定状态,则不做任何操作,否则将其置为指定状态并更新工作线程的数量
* @param targetState 指定状态
*/
private void advanceRunState(int targetState) {
for (;;) {
int c = ctl.get();
if (runStateAtLeast(c, targetState) ||
ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
break;
}
}
/**
* 尝试终止线程池
* 回收线程
* 从TIDYING过渡到TERMINATED状态
*
* STOP(线程池为空):TIDYING
* SHUTDOWN(线程池和队列为空):TIDYING
*/
final void tryTerminate() {
for (;;) {
int c = ctl.get();
/**
* 1. 从上面的线程池状态图可知,RUNNING是不可能直接过渡到TERMINATED,只能从TIDYING过渡到TERMINATED
* 2. 满足TIDYING状态只能是STOP且线程池为空或SHUTDOWN且线程池和队列为空
* 3. 当调用shutdown方法时,线程池的状态是SHUTDOWN,但是当队列中还有任务未执行时,回收所有空闲的工作线程!!!(有些空闲线程在被中断时因为队列中有任务而变成不是空闲),这正好符合我们说SHUTDOWN状态下的线程池会去处理队列中* 的任务,当队列中的任务执行完毕后在回收所有线程!!!
* 4. 当调用shutdownNow方法时,线程池的状态是STOP,此时队列中的任务将会被清空,同时回收所有线程
*/
if (isRunning(c) || runStateAtLeast(c, TIDYING) || (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return;
if (workerCountOf(c) != 0) { //工作线程数大于0
interruptIdleWorkers(ONLY_ONE); //中断其中一个空闲线程,即使只是中断一个空闲线程,后续还会继续去中断其他空闲线程
return;
}
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) { //将线程池的状态变成TIDYING状态
try {
terminated();//调用terminated方法,交给每个子类去实现
} finally {
ctl.set(ctlOf(TERMINATED, 0)); //将线程池的状态变成TERMINATED状态
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
}
}
/**
* 根据指定值判断是中断所有空闲线程还是只中断其中一个
* 即使是只中断一个空闲线程,后续也会因为再次调用tryTerminate#interruptIdleWorkers在中断其他空闲线程,所以并不影响
* @param onlyOne 标志
*/
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
if (!t.isInterrupted() && w.tryLock()) { //若工作线程被上锁了,说明工作线程正在执行任务,不能去中断,不属于空闲线程
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
/**
* 中断所有工作线程
*/
private void interruptWorkers() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers)
w.interruptIfStarted();
} finally {
mainLock.unlock();
}
}
/**
* 直接拒绝新任务提交的策略
* @param command 任务
*/
final void reject(Runnable command) {
handler.rejectedExecution(command, this);
}
/**
* 根据指定值决定是要判断线程池是处于RUNNING还是SHUTDOWN
* @param shutdownOK true表示要判断线程池是否处于SHUTDOWN,若是则返回true
* @return 结果值
*/
final boolean isRunningOrShutdown(boolean shutdownOK) {
int rs = runStateOf(ctl.get());
return rs == RUNNING || (rs == SHUTDOWN && shutdownOK);
}
/**
* 移除队列中的所有任务,并将其放入到数组中
* 通常使用drainTo将任务放入到数组中. 但是,如果队列是DelayQueue或其他类型的队列,poll或drainTo可能无法删除某些元素,则将它们逐个删除.
* @return 存放任务的数组
*/
private List<Runnable> drainQueue() {
BlockingQueue<Runnable> q = workQueue;
ArrayList<Runnable> taskList = new ArrayList<Runnable>();
q.drainTo(taskList);
if (!q.isEmpty()) {
for (Runnable r : q.toArray(new Runnable[0])) {
if (q.remove(r))
taskList.add(r);
}
}
return taskList;
}
/**
* 执行任务
* 有可能是创建新线程去执行任务,也有可能是复用线程去执行任务
* 若线程池已经关闭或者线程池饱和(线程个数达到最大线程数且队列已满)则会执行拒绝任务的策略,默认情况下是抛出异常,其他情况下都会正常执行任务
* 注意:若线程池在关闭前就已经将任务入队列且没有remove成功,那么最终还是要正常执行
*
* 总结:
*
* 线程池的状态处于RUNNING下:
* 1. 对于新提交的任务,线程个数小于核心线程数,则新创建线程进行处理 (size < corePoolSize)
* 2. 对于新提交的任务,线程个数大于核心线程小于最大线程数,则尝试放入到队列中 (corePoolSize < size < maximumPoolSize)
* 21. 若此时队列未满,则放入成功,后续会正常执行
* 22. 若此时队列已满(放入失败),则直接创建新线程去执行
* 3. 对于新提交的任务,线程个数大于核心线程数且等于最大线程数,则尝试放入到队列中,若队列已经满了,这时的线程池已经饱和了,只能拒绝任务,若队列未满,则加入到队列中
*
* 线程处的状态已经关闭,这里有个点需要注意下,应该采用的双重检查,所以以下的分析会说是第一次检查时关闭还是第二次检查时关闭
* 1. 对于新提交的任务,线程个数小于核心线程数,拒绝任务
* 2. 对于新提交的任务,线程个数大于核心线程数小于最大线程数,第一次检查时线程池已关闭,说明还未入队列中,拒绝任务
* 2. 对于新提交的任务,线程个数大于核心线程数小于最大线程数,第一检查处于RUNNING,入队列中,第二次检查时关闭了,去移除任务,要是移除失败了,那么最终还是会去正常执行任务,若移除成功了,则拒绝任务
*
* @param command 任务
*/
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
int c = ctl.get();
/**
* 1. 若线程池中的线程个数小于核心线程数,则创建一个新线程去执行任务,新线程中包含有任务
* 2. 若线程个数大于核心线程数,则判断线程池是否处于RUNNING,且尝试去入队列,这里要注意下双重检查的处理
* 21. 若第二次检查时线程池关闭了,照道理应该remove任务,不过有可能移除失败,没办法还是必须去处理它
* 22. 若线程池处于RUNNING,则正常处理
* 3. 若线程池并不处于RUNNING,即第一次检查时就关闭了,不好意思了,拒绝任务
* 4. 若线程池处于RUNNING,但是入队列失败了,那么判断是否有多余的线程,若没有,则说明线程池达到饱和,只能拒绝任务了,若有则创建新线程去执行任务
*/
if (workerCountOf(c) < corePoolSize) { //1
if (addWorker(command, true)) //若创建新线程失败或抛出异常,则返回false,addWorker方法中会判断线程池的状态是否处于RUNNING
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {//2 workQueue.offer返回true表示入队列成功
int recheck = ctl.get();
/**
* 双重检查,上面的注释说的是:线程池在第一次检查之后就被关闭了,有可能是多线程下导致的,若是第二次检查中线程池已经关闭了,那么就要把之前入队列时的任务移除掉,并使用了拒绝策略
*/
if (! isRunning(recheck) && remove(command))
reject(command);
/**
* 双重检查,此时有两种情况:
* 1. 线程池的状态处于RUNNING,但是却没有工作线程,那么队列中还有刚才添加的任务没处理呢,所以只能在创建一个新线程来处理任务了
* 2. 线程池已经关闭了,那么照道理应该移除掉队列中的任务,但是要是没移除掉呢,也就是remove返回false,那它还是会新创建一个线程去处理队列中的任务
*/
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
/**
* 此时有两种情况:
* 1. 线程池的状态处于SHUNDOWN/STOP/TIDYING/TERMINATED,说明线程池已经关闭了,那么该任务已经是没有入到队列中,所以最终会返回false,所以直接执行拒绝策略
* 2. 线程池的状态处于RUNNING,但是任务入队列失败了,有可能是队列满了,也有可能是异常发生了,那么它会在addWorker中判断是否有多余的线程可以创建(与最大线程数比较),若有则正常执行任务,若没有,不好意思了,拒绝任务
*/
else if (!addWorker(command, false))//3,4
reject(command);
}
/**
* 创建工作线程
* 若线程池处于SHUTDOWN或STOP,则返回false
* 若线程工程类创建线程失败或返回null或抛出异常,则返回false
* @param firstTask 工作线程的首个任务,若firstTask = null表示只是为了创建一个新线程去处理队列中的任务,若firstTask != null表示正是为了处理指定任务而创建的新线程
* @param core true表示使用核心线程数作为判断线程个数的标准,false表示使用最大线程数作为判断线程个数的标准
* @return 是否成功创建工作线程
*/
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
/**
* 1. 若线程池已经关闭且队列中已经没有任务了,那么应该返回false
* 2. 若线程池已经关闭但队列中还有任务,那么应该创建新线程去执行,这种情况下是没有工作线程下才会调用此方法
*/
if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) //若线程个数已经超过标准了,是不会在创建了,直接返回false
return false;
if (compareAndIncrementWorkerCount(c)) //增加工作线程的数量
break retry;
c = ctl.get();
if (runStateOf(c) != rs) //走到这里说明,线程池的状态或者工作线程个数发生了改变
continue retry;
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask); //创建工作线程
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//双重检查
int rs = runStateOf(ctl.get());
/**
* 此时有两种情况:
* 1. 线程池的状态处于RUNNING,为了执行任务而创建新的线程
* 2. 线程池已经关闭,但是队列中还有任务(若队列中没任务了则在上面就已经返回false了),所以必须要创建一个新线程去执行队列中的任务(此时的情况是没有工作线程了必须要创建一个,若还存在工作线程,压根就不会调用此方法)
* 3. 线程池已经关闭,firstTask != null表示要执行新提交的任务,那么这个时候是拒绝的,而若firstTask = null表示创建新线程是为了执行队列中的任务,如第2点
*/
if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive())
throw new IllegalThreadStateException();
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true; //表示创建工作线程成功
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start(); //通过该方式去启动工作线程
workerStarted = true;
}
}
} finally {
if (! workerStarted) //线程工程类创建线程失败或返回null或抛出异常,线程池已关闭拒绝新提交的任务,线程启动失败
addWorkerFailed(w);
}
return workerStarted;
}
/**
* 处理创建工作线程失败后的动作
* @param w 工作线程
*/
private void addWorkerFailed(Worker w) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (w != null)
workers.remove(w); //移除指定工作线程
decrementWorkerCount();
tryTerminate();//
} finally {
mainLock.unlock();
}
}
/**
* 回收工作线程
* 有可能是线程池处于RUNNING却因为工作线程突发异常导致被回收,有可能是因为关闭了线程池导致回收线程
* @param w 工作线程
* @param completedAbruptly 工作线程是否发生异常
*/
private void processWorkerExit(Worker w, boolean completedAbruptly) {
if (completedAbruptly) //若突发异常导致工作线程发生异常,该值为true,应该主动调用方法去减少工作线程的数量,正常情况下工作线程在被回收时会在指定方法中调用getTask -> decrementWorkerCount
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
completedTaskCount += w.completedTasks;
workers.remove(w); //移除指定工作线程
} finally {
mainLock.unlock();
}
tryTerminate(); //尝试去终止线程池
int c = ctl.get();
if (runStateLessThan(c, STOP)) { //线程池的状态处于SHUTDONW/RUNNING
if (!completedAbruptly) {
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && ! workQueue.isEmpty()) //不存在工作线程,但队列中还有任务未处理
min = 1;
if (workerCountOf(c) >= min) //若线程个数满足上面的条件则不需要创建新线程了
return;
}
/**
* 有可能因为工作线程发生异常导致被回收,线程池中不存在工作线程,但此时队列中还有任务,所以必须要创建新线程去处理队列中的任务
* 按照线程池的策略,原先线程个数大于核心线程数在被回收到导致变成了小于,那么它会弥补新的一个线程,同样的原先线程个数小于核心线程数,它也会弥补新的一个线程,但若是即使被回收了也仍是大于,则不会新增了
* 按照我的理解它是为了保证不会降低效率
*/
addWorker(null, false);
}
}
/**
* 获取队列中的任务
* 工作线程一直处理阻塞或阻塞指定时间
*
* 总结:
* 1. 线程个数超过最大线程数(重新调用setMaximumPoolSize),会返回null
* 2. 线程池处于SHUTDOWN且队列为空,返回null
* 3. 线程池处于STOP,返回null
* 4. 线程个数小于最大线程数,线程池处于RUNNING
* 41. 若设置了allowCoreThreadTimeOut = true,所有工作线程的空闲时间超过保活时间后会被回收
* 42. 若设置了allowCoreThreadTimeOut = false,且线程个数大于核心线程数的工作线程的空闲时间超过保活时间会被回收,即最终只有核心线程数的工作线程存在,通过调用shutdown回收最后的工作线程
* 5. 若队列中还有任务存在,不管是哪一种情况至少都会有一个工作线程存在
*
* @return 任务
*/
private Runnable getTask() {
boolean timedOut = false; //表示工作线程的空闲时间是否超过了指定时间
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
/**
* 1. 线程池处于SHUTDOWN,但是队列中还有任务(我还要工作),但如果队列为空,说明已经没有任务了,该工作线程可以被回收了
* 2. 线程池处于STOP,队列中的任务实际上已经被清空了,也不会允许任务的提交,该工作线程也可以被回收了
*/
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);//获取线程个数
/**
* 线程池处于RUNNING,那么就该考虑怎么回收工作线程了,注意keepAliveTime > 0 否则不管如何都回收不了工作线程
* 1. 若设置allowCoreThreadTimeOut = true,则当所有的工作线程的空闲时间超过指定时间后会被回收,即所有的工作线程都会被回收
* 2. 若设置allowCoreThreadTimeOut = false,线程个数大于核心线程数的工作线程的空闲时间超过指定时间后会被回收,即最终的工作线程个数等于核心线程数
*/
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
/**
* 1. 工作线程数超过最大线程数,自然要被回收了,工作线程太多了
* 2. 线程个数小于最大线程数,工作线程的空闲时间是否超过了指定时间,若没超过则不会进入if语句中了,表示当前的工作线程要继续干活
* 若超过指定时间了,那么还要确认下自己是否是最后一个工作线程,若是最后一个工作线程就要判断队列中是否还有任务,毕竟不能所有工作线程都被回收了,总要有人留下来处理任务吧,若不存在任务则被回收,若存在就要留下来为队列中的任* 务服务了,有点难受了,若不是最后一个线程,先走为妙,总有人要留下来背锅的,哈哈哈,所以也会被回收
*/
if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c)) //工作线程被回收时要减少数量
return null;
continue;
}
try {
/**
* 这里涉及到两种策略:注意keepAliveTime > 0 否则不管如何都回收不了工作线程
* 1. 工作线程要受保活时间的限制进行回收,这种情况下有两个可能,第一种是设置了allowCoreThreadTimeOut = true,即所有的线程都要受保活时间的限制,另外一种是allowCoreThreadTimeOut = false,且线程个数大于核心线程数
* 了,有些工作线程需要被回收,这两种的线程都会被回收,即timed = true,poll方法表示我只等指定长时间,超过后就返回了(阻塞指定时间)
* 2. 工作线程不受保活时间的限制,allowCoreThreadTimeOut = false,且线程个数小于核心线程数,此时timed = false,take会一直阻塞,直到被中断
*/
Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take(); //从队列中获取任务,若获取到直接返回,若获取不到任务,则会阻塞一段时间或一直阻塞
if (r != null)
return r;
timedOut = true; //走到这里说明阻塞一点时间后仍没有任务,继续循环判断是否要被回收还要是继续等待
} catch (InterruptedException retry) { //工作线程阻塞时被中断了
timedOut = false;
}
}
}
/**
* 执行工作线程
* 若getTask返回null会跳出循环导致工作线程被回收
* 可能因为抛出异常导致工作线程被回收,如beforeExecute/afterExecute
* 通过afterExecute/UncaughtExceptionHandler可知道任务发生异常的信息
* @param w 工作线程
*/
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock();
boolean completedAbruptly = true; //是否因为发生异常导致工作线程被回收
try {
//重复从队列中获取任务去执行,若当前工作线程已经执行完自身的任务就要帮忙去处理队列中的任务
while (task != null || (task = getTask()) != null) {
w.lock();
/**
* 双重检查
* 在第一次检查STOP时,线程池的状态处于STOP,既然后续外部代码改变了中断状态,这里的代码也会再次判断,总而言之结果都会中断
* 在第一次检查STOP时,线程池的状态处于RUNNING/SHUTDOWN,Thread.interrupted这个语句是必须要有的,假设外部代码会改变中断状态,当你调用shutdown##interruptIdleWorkers时可能会出现线程从来都没有被中断过(中断被清除)* 那么将导致一直阻塞,所以这里加上Thread.interrupted是为了消除外部代码的干扰,这也导致了工作线程的中断状态被重置了,对于此时线程池处于STOP,它的中断状态就不对了,所以又做了第二次检查就是将其修改成中断状态
* 以上的解释纯属个人理解!!!
*/
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); //回收工作线程,有可能是因为发生异常,有可能是设置了保活时间等
}
}
/**
* 关闭线程池
* 有可能该方法已经调用结束了但任务还未执行完,不过这不影响
* 队列中的任务会继续被执行,但是会拒绝新任务的提交
*/
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(SHUTDOWN); //将线程池的状态设置为SHUTDOWN
interruptIdleWorkers(); //中断所有空闲线程,相当于一直阻塞代码将不会继续阻塞了
onShutdown();
} finally {
mainLock.unlock();
}
tryTerminate();//回收所有空闲线程
}
/**
* 停止线程池并返回队列中的任务(未执行的任务)
* 尽最大的努力去中断所有正在执行的任务,同时移除队列中的任务,拒绝新任务的提交
* @return 队列中的任务
*/
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();
}
tryTerminate();//回收线程
return tasks;
}
/**
* 线程池是否处于STOP或SHUTDOWN
* @return 结果值
*/
public boolean isShutdown() {
return ! isRunning(ctl.get());
}
/**
* 线程处是否处于SHUTDOWN/STOP/TIDYING
* @return 结果值
*/
public boolean isTerminating() {
int c = ctl.get();
return ! isRunning(c) && runStateLessThan(c, TERMINATED);
}
/**
* 线程池是否处于TERMINATED
* @return 结果值
*/
public boolean isTerminated() {
return runStateAtLeast(ctl.get(), TERMINATED);
}
/**
* 允许等待指定时间后获取线程池是否处于TERMINATED
* @param timeout 等待时间
* @param unit 时间单位
* @return 结果值
*/
public boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (;;) {
if (runStateAtLeast(ctl.get(), TERMINATED)) //线程池是否处于TERMINATED
return true;
if (nanos <= 0)
return false;
nanos = termination.awaitNanos(nanos); //等待指定时间
}
} finally {
mainLock.unlock();
}
}
/**
* 当线程池不在被引用/使用和没有工作线程的情况下调用shutdown
*/
protected void finalize() {
SecurityManager sm = System.getSecurityManager();
if (sm == null || acc == null) {
shutdown();
} else {
PrivilegedAction<Void> pa = () -> { shutdown(); return null; };
AccessController.doPrivileged(pa, acc);
}
}
/**
* 设置线程的工厂类
* @param threadFactory 线程的工厂类
*/
public void setThreadFactory(ThreadFactory threadFactory) {
if (threadFactory == null)
throw new NullPointerException();
this.threadFactory = threadFactory;
}
/**
* 获取线程的工厂类
* @return 线程的工厂类
*/
public ThreadFactory getThreadFactory() {
return threadFactory;
}
/**
* 设置拒绝任务的策略
* @param handler 策略
*/
public void setRejectedExecutionHandler(RejectedExecutionHandler handler) {
if (handler == null)
throw new NullPointerException();
this.handler = handler;
}
/**
* 获取拒绝任务的策略
* @return 策略
*/
public RejectedExecutionHandler getRejectedExecutionHandler() {
return handler;
}
/**
* 设置核心线程数
* @param corePoolSize 核心线程数
*/
public void setCorePoolSize(int corePoolSize) {
if (corePoolSize < 0)
throw new IllegalArgumentException();
int delta = corePoolSize - this.corePoolSize;
this.corePoolSize = corePoolSize;
if (workerCountOf(ctl.get()) > corePoolSize) //若当前线程池的线程个数超过指定的核心线程数,则回收所有空闲线程
interruptIdleWorkers();
else if (delta > 0) { // 若当前线程池的线程个数小于指定的核心线程数
/**
* 新增工作线程无非就是为了能够帮忙处理队列中的任务,所以当队列中任务个数小于要新增的线程个数时,那就不要新增那么多工作线程了,这是要浪费的节奏
* 当队列中的任务个数大于要新增的线程个数时,不好意思了,最多只能增加到指定的核心线程数
*/
int k = Math.min(delta, workQueue.size());
while (k-- > 0 && addWorker(null, true)) {
if (workQueue.isEmpty())//当队列被清空了,就不需要再新增工作线程了
break;
}
}
}
/**
* 获取核心线程数
* @return 核心线程数
*/
public int getCorePoolSize() {
return corePoolSize;
}
/**
* 预先创建并开启线程,等待任务到来
* @return false表示所有核心线程都已开启,true表示已经创建并开启一个线程
*/
public boolean prestartCoreThread() {
return workerCountOf(ctl.get()) < corePoolSize &&
addWorker(null, true);
}
/**
* 预先创建并开启至少一个线程,即使核心线程数是0
*/
void ensurePrestart() {
int wc = workerCountOf(ctl.get());
if (wc < corePoolSize)
addWorker(null, true);
else if (wc == 0)
addWorker(null, false);
}
/**
* 预先创建并开启所有核心线程数的工作线程
* @return 创建并开启线程的个数
*/
public int prestartAllCoreThreads() {
int n = 0;
while (addWorker(null, true))
++n;
return n;
}
/**
* 获取是否所有线程都受保活时间的限制
* @return 是否所有线程都受保活时间的限制
*/
public boolean allowsCoreThreadTimeOut() {
return allowCoreThreadTimeOut;
}
/**
* 设置是否所有线程都受保活时间的限制
* 若为false表示核心线程个数的线程不会受保活时间的限制,但若线程个数超过了核心线程个数就会受到限制,直到线程个数降低到核心线程个数
* 若为true表示所有的线程都受保活时间的限制,即线程的空闲时间超过保活时间则会被回收
* @param value 值
*/
public void allowCoreThreadTimeOut(boolean value) {
if (value && keepAliveTime <= 0)
throw new IllegalArgumentException("Core threads must have nonzero keep alive times");
if (value != allowCoreThreadTimeOut) {
allowCoreThreadTimeOut = value;
if (value)
interruptIdleWorkers(); //回收空闲线程
}
}
/**
* 设置最大线程数
* 若线程池中的线程个数大于指定的最大线程数,则它们会在下次空闲时被回收
* @param maximumPoolSize 最大线程数
*/
public void setMaximumPoolSize(int maximumPoolSize) {
if (maximumPoolSize <= 0 || maximumPoolSize < corePoolSize)
throw new IllegalArgumentException();
this.maximumPoolSize = maximumPoolSize;
if (workerCountOf(ctl.get()) > maximumPoolSize)
interruptIdleWorkers();
}
/**
* 获取最大线程数
* @return 最大线程数
*/
public int getMaximumPoolSize() {
return maximumPoolSize;
}
/**
* 设置空闲线程的保活时间
* @param time 时间
* @param unit 时间单位
*/
public void setKeepAliveTime(long time, TimeUnit unit) {
if (time < 0)
throw new IllegalArgumentException();
if (time == 0 && allowsCoreThreadTimeOut())
throw new IllegalArgumentException("Core threads must have nonzero keep alive times");
long keepAliveTime = unit.toNanos(time);
long delta = keepAliveTime - this.keepAliveTime;
this.keepAliveTime = keepAliveTime;
if (delta < 0)
interruptIdleWorkers();
}
/**
* 获取保活时间
* @param unit 时间单位
* @return 保活时间
*/
public long getKeepAliveTime(TimeUnit unit) {
return unit.convert(keepAliveTime, TimeUnit.NANOSECONDS);
}
/**
* 获取队列
* 获取队列主要是为了调试与监控,该队列一直在变化着
* @return 队列
*/
public BlockingQueue<Runnable> getQueue() {
return workQueue;
}
/**
* 从队列中移除指定任务
* 有些情况下队列中并不是直接存放任务,有可能任务被包装进另外一种形式的对象中,然后在将该对象放入队列中,这种情况下不能使用该方法去移除,而应该是purgee方法
* @param task 任务
* @return 是否移除成功
*/
public boolean remove(Runnable task) {
boolean removed = workQueue.remove(task);
tryTerminate();
return removed;
}
/**
* 从队列中移除已取消的任务
*/
public void purge() {
final BlockingQueue<Runnable> q = workQueue;
try {
Iterator<Runnable> it = q.iterator();
while (it.hasNext()) {
Runnable r = it.next();
if (r instanceof Future<?> && ((Future<?>)r).isCancelled())
it.remove();
}
} catch (ConcurrentModificationException fallThrough) {
for (Object r : q.toArray())
if (r instanceof Future<?> && ((Future<?>)r).isCancelled())
q.remove(r);
}
tryTerminate();
}
/**
* 获取线程池中工作线程的个数
* @return 工作线程的个数
*/
public int getPoolSize() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Remove rare and surprising possibility of
// isTerminated() && getPoolSize() > 0
return runStateAtLeast(ctl.get(), TIDYING) ? 0 : workers.size();
} finally {
mainLock.unlock();
}
}
/**
* 获取正在执行任务的工作线程的个数
* @return 正在执行任务的工作线程的个数
*/
public int getActiveCount() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
int n = 0;
for (Worker w : workers)
if (w.isLocked()) //工作线程上锁表示正在执行任务
++n;
return n;
} finally {
mainLock.unlock();
}
}
/**
* 获取线程池中曾经存在的最大线程数
* @return 结果值
*/
public int getLargestPoolSize() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
return largestPoolSize;
} finally {
mainLock.unlock();
}
}
/**
* 获取线程池中所有任务的个数,任务包括已执行、正在执行、未执行
* @return 结果值
*/
public long getTaskCount() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
long n = completedTaskCount;
for (Worker w : workers) {
n += w.completedTasks;
if (w.isLocked())
++n;
}
return n + workQueue.size();
} finally {
mainLock.unlock();
}
}
/**
* 获取线程池中已执行的任务的个数
* @return 结果值
*/
public long getCompletedTaskCount() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
long n = completedTaskCount;
for (Worker w : workers)
n += w.completedTasks;
return n;
} finally {
mainLock.unlock();
}
}
/**
* 当线程池拒绝任务时,采用只要线程池未关闭,由调用execute的线程运行任务,若线程池已关闭,则任务将被丢弃
*/
public static class CallerRunsPolicy implements RejectedExecutionHandler {
public CallerRunsPolicy() { }
/**
* 只要线程池未关闭,由调用execute的线程运行任务,若线程池已关闭,则任务将被丢弃
* @param r 任务
* @param e 线程池对象
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
/**
* 当线程池拒绝任务时,直接抛出RejectedExecutionException异常
*/
public static class AbortPolicy implements RejectedExecutionHandler {
public AbortPolicy() { }
/**
* 直接抛出异常
* @param r 任务
* @param e 线程池对象
* @throws RejectedExecutionException always
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString());
}
}
/**
* 当线程池拒绝任务时,直接丢弃任务
*/
public static class DiscardPolicy implements RejectedExecutionHandler {
public DiscardPolicy() { }
/**
* 直接丢弃任务
* @param r 任务
* @param e 线程池对象
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
/**
* 当线程池拒绝任务时,采用只要线程池未关闭,则将丢弃队列中头部位置的任务(最早入队列的任务),然后重复执行指定任务
*/
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
public DiscardOldestPolicy() { }
/**
* 只要线程池未关闭,则将丢弃队列中头部位置的任务(最早入队列的任务),然后重复执行指定任务
* @param r 任务
* @param e 线程池对象
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}
/**
* 同下,此方法会一直阻塞直到有任务完成,所有照道理不会cathc到TimeoutException异常才对
* @param tasks 任务列表
* @param timed 是否等待一段时间,false:将会一直等待有任务完成
* @param nanos 等待时间,以纳秒为时间单位
* @return 结果值
*/
public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException {
try {
return doInvokeAny(tasks, false, 0);
} catch (TimeoutException cannotHappen) {
assert false;
return null;
}
}
/**
* 执行集合中的任务,出现以下的情况将不会在执行剩余的任务,也就是说,至始至终只会有一个任务会完成,若一直没有任务完成且未设置等待时间,则一直等待
* 1、其中一个任务完成,则取消其他执行中的任务
* 2、等待一段时间后退出并取消任务
* 3、所有的任务都抛出异常
* @param tasks 任务列表
* @param timed 是否等待一段时间,false:将会一直等待有任务完成
* @param nanos 等待时间,以纳秒为时间单位
* @return 结果值
*/
private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks, boolean timed, long nanos)
throws InterruptedException, ExecutionException, TimeoutException {
if (tasks == null)
throw new NullPointerException();
int ntasks = tasks.size();
if (ntasks == 0)
throw new IllegalArgumentException();
ArrayList<Future<T>> futures = new ArrayList<Future<T>>(ntasks);
ExecutorCompletionService<T> ecs = new ExecutorCompletionService<T>(this); //该类没什么特别,只是将已完成的任务放到队列中,以便可以获取,底层实际上还是靠线程池去执行任务
try {
ExecutionException ee = null;
final long deadline = timed ? System.nanoTime() + nanos : 0L;
Iterator<? extends Callable<T>> it = tasks.iterator();
futures.add(ecs.submit(it.next())); //先尝试提交一个任务
--ntasks; //未提交的任务个数
int active = 1;//执行中的任务个数
for (;;) {
Future<T> f = ecs.poll(); //尝试获取之前提交的任务是否完成,若是已完成则f将不会空,若未完成则f为空
if (f == null) {
if (ntasks > 0) { //是否还有未提交的任务,在尝试提交一个
--ntasks;
futures.add(ecs.submit(it.next()));
++active;
}
else if (active == 0) //若已经不存在执行中的任务,说明从一开始到现在都没有任务完成或任务都抛出异常了,已经没有未提交的任务了,也不存在执行中的任务了,直接退出
break;
else if (timed) { //是否尝试等待一段时间去获取已完成的任务
// 走到这里说明已没有未提交的任务了,现在就等着执行中的任务执行完成,不过它不是一直等着,而是等待一段时间后若任务还是未完成,那么将抛出异常来直接退出,不在等待了
f = ecs.poll(nanos, TimeUnit.NANOSECONDS);
if (f == null)
throw new TimeoutException();
nanos = deadline - System.nanoTime();
}
else
f = ecs.take(); //走到这里说明已经没有未提交的任务了,现在就一直等着执行中的任务执行完成,然后获取它就可以了,简单来说就是这里会被阻塞住,直到队列中添加了已完成的任务
}
if (f != null) { //说明有任务已经完成
--active;
try {
return f.get(); //直接获取结果
} catch (ExecutionException eex) { //记录异常信息
ee = eex;
} catch (RuntimeException rex) {
ee = new ExecutionException(rex);
}
}
}
if (ee == null)
ee = new ExecutionException(); //走到这里说明所有的任务都抛出异常了
throw ee;
} finally {
for (int i = 0, size = futures.size(); i < size; i++)
futures.get(i).cancel(true); //1、有任务已完成,取消剩余的执行中的任务 2、所有的任务都抛出异常了 3、等待一段时间后而退出的,则取消任务
}
}
/**
* 同下,此方法会阻塞一段时间,有可能抛出TimeoutException异常
* @param tasks 任务列表
* @param timed 是否等待一段时间,false:将会一直等待有任务完成
* @param nanos 等待时间,以纳秒为时间单位
* @return 结果值
*/
public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
return doInvokeAny(tasks, true, unit.toNanos(timeout));
}
/**
* 执行集合中的所有任务,若有一个任务被中断,则取消所有任务
* @param tasks 任务列表
* @return 结果值,若是抛出异常则说明任务被中断了,若没有抛出异常则说明所有任务都正常完成了
*/
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException {
if (tasks == null)
throw new NullPointerException();
ArrayList<Future<T>> futures = new ArrayList<Future<T>>(tasks.size()); //保存所有任务
boolean done = false;
try {
for (Callable<T> t : tasks) {
RunnableFuture<T> f = newTaskFor(t);
futures.add(f);
execute(f); //执行任务
}
for (int i = 0, size = futures.size(); i < size; i++) {
Future<T> f = futures.get(i);
if (!f.isDone()) {
try {
f.get();
} catch (CancellationException ignore) {
} catch (ExecutionException ignore) {
}
}
}
done = true;
return futures;
} finally {
if (!done)
for (int i = 0, size = futures.size(); i < size; i++) //走到这里的话说明有任务被中断了,取消所有任务,已完成的任务没办法取消,这块内容跟Future有关
futures.get(i).cancel(true);
}
}
/**
* 执行集合中的所有任务,若有一个任务被中断或等待超时了,则取消任务,已完成的任务没办法取消
* 参数中的时间包括提交任务的时间 + 等待任务的时间
* 该方法有可能因为等待超时而返回、也有可能因为被中断而返回、也有可能正常返回,对于中断我们可以在外层通过catch判断,但是剩下的两个情况我们没办法判断是否任务正常完成了,除非自己在调用isCancelled去一个一个判断
* @param tasks 任务列表
* @param timeout 等待时间
* @param unit 时间单位
*/
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException {
if (tasks == null)
throw new NullPointerException();
long nanos = unit.toNanos(timeout);
ArrayList<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());//保存所有任务
boolean done = false;
try {
for (Callable<T> t : tasks)
futures.add(newTaskFor(t));
final long deadline = System.nanoTime() + nanos;
final int size = futures.size();
for (int i = 0; i < size; i++) {
execute((Runnable)futures.get(i));
nanos = deadline - System.nanoTime();
if (nanos <= 0L)
return futures;
}
for (int i = 0; i < size; i++) {
Future<T> f = futures.get(i);
if (!f.isDone()) {
if (nanos <= 0L)
return futures;
try {
f.get(nanos, TimeUnit.NANOSECONDS); //等待一段时间
} catch (CancellationException ignore) {
} catch (ExecutionException ignore) {
} catch (TimeoutException toe) {
return futures;
}
nanos = deadline - System.nanoTime();
}
}
done = true;
return futures;
} finally {
if (!done)
for (int i = 0, size = futures.size(); i < size; i++) //走到这里说明有任务被中断了或等待时间超时了
futures.get(i).cancel(true);
}
}
/**
* 生成异步形式的任务,为了异步获取结果
* 关于Future的知识点将会另外新起文章进行讲解
* 这里为啥会多一个value呢? 与下面的重载方法相比,Runnable并不存在返回值,故提供了此值,相当于Runnable + value = Callable
* @param runnable 任务
* @param value 结果值
* @return 异步形式的任务
*/
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
/**
* 生成异步形式的任务,为了异步获取结果
* @param callable 任务
* @return 异步形式的任务
*/
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
/**
* 提交任务并执行,返回异步对象来获取结果
* @param task 任务
* @return 异步对象
*/
public Future<?> submit(Runnable task) {
if (task == null)
throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
/**
* 提交任务并执行,返回异步对象来获取结果
* @param task 任务
* @param result 结果值
* @return 异步对象
*/
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
}
/**
* 提交任务并执行,返回异步对象来获取结果
* @param task 任务
* @return 异步对象
*/
public <T> Future<T> submit(Callable<T> task) {
if (task == null)
throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
总结
-
线程池的状态处于
RUNNING下:-
对于新提交的任务,
线程个数小于核心线程数,则新创建线程进行处理 (size < corePoolSize)。 -
对于新提交的任务,
线程个数大于核心线程小于最大线程数,则尝试放入到队列中 (corePoolSize < size < maximumPoolSize)。-
若此时队列未满,则放入成功,后续会正常执行。
-
若此时队列已满(放入失败),则直接创建新线程去执行。
-
-
对于新提交的任务,
线程个数大于核心线程数且等于最大线程数,则尝试放入到队列中,若队列已经满了,这时的线程池已经饱和了,只能拒绝任务,若队列未满,则加入到队列中。
-
-
线程个数小于最大线程数,线程池处于RUNNING:
-
若设置了allowCoreThreadTimeOut = true,所有工作线程的空闲时间超过保活时间后会被回收。
-
若设置了allowCoreThreadTimeOut = false,且线程个数大于核心线程数的工作线程的空闲时间超过保活时间会被回收,即最终只有核心线程数的工作线程存在,通过调用shutdown回收最后的工作线程。
-
-
若队列中还有任务存在,不管是哪一种情况至少都会有一个工作线程存在。
-
线程池的状态——RUNNING/SHUTDONW/STOP/TIDYING/TERMINATED
-
线程池
拒绝任务的策略:-
ThreadPoolExecutor.AbortPolicy:当线程池拒绝任务时,采用直接抛出RejectedExecutionException异常。
-
ThreadPoolExecutor.CallerRunsPolicy:当线程池拒绝任务时,采用只要线程池未关闭,由调用execute的线程运行任务,这提供了一种反馈机制,而若线程池已关闭,则任务将被丢弃。
-
ThreadPoolExecutor.DiscardPolicy:当线程池拒绝任务时,采用直接丢弃了任务,相当于直接不执行任务了,很干脆。
-
ThreaPollExecutor.DiscardOldestPolicy:当线程池拒绝任务时,采用只要线程池未关闭,则将丢弃队列中头部位置的任务(最早入队列的任务),然后重复执行指定任务(调用execute方法,有可能再次被拒绝导致又重复执行)。
-
-
invokeAny与invokeAll的使用与区别。
ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor是家族中的第二骨干,能够延迟或周期性执行任务,由于其继承了ThreadPoolExecutor类,故而它也拥有线程池状态的概念!
数据结构
public class ScheduledThreadPoolExecutor extends ThreadPoolExecutor implements ScheduledExecutorService {
/**
* 当线程池处于SHUTDOWN状态时是否继续执行周期性任务-scheduleWithFixedDelay/scheduleAtFixedRate
* 默认情况是false,表示不会继续执行周期性任务
*/
private volatile boolean continueExistingPeriodicTasksAfterShutdown;
/**
* 当线程池处于SHUTDOWN状态时是否执行延迟任务
* 默认情况下是true,表示会执行延迟任务
*/
private volatile boolean executeExistingDelayedTasksAfterShutdown = true;
/**
* 当任务被成功取消时是否立即从队列中删除此任务
* 默认情况是false,表示已取消的任务不会马上从队列中删除,只有在关闭线程池的时候才会清除,所以建议将其设置成true
*/
private volatile boolean removeOnCancel = false;
/**
* 每个任务的序号,依次呈现递增
*/
private static final AtomicLong sequencer = new AtomicLong();
}
构造方法
/**
* 初始化,采用延迟队列,该队列中使用了数组作为其数据结构,会自动扩容,所以相当于是个无界队列,设置最大线程池没效果
* @param corePoolSize 核心线程数
*/
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue());
}
/**
* 初始化,采用延迟队列,自定义线程的工厂类
* @param corePoolSize 核心线程数
* @param threadFactory 线程的工厂类
*/
public ScheduledThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue(), threadFactory);
}
/**
* 初始化,采用延迟队列,设置拒绝任务的策略
* @param corePoolSize 核心线程数
* @param handler 拒绝任务的策略
*/
public ScheduledThreadPoolExecutor(int corePoolSize, RejectedExecutionHandler handler) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue(), handler);
}
/**
* 初始化,采用延迟队列,设置拒绝任务的策略,自定义线程的工厂类
* @param corePoolSize 核心线程数
* @param threadFactory 线程的工厂类
* @param handler 拒绝任务的策略
*/
public ScheduledThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue(), threadFactory, handler);
}
简单方法
/**
* 立即执行任务
* @param command 任务
*/
public void execute(Runnable command) {
schedule(command, 0, NANOSECONDS);
}
/**
* 延迟指定的时间后执行任务
* @param command 任务
* @param delay 延迟时间
* @param unit 时间单位
* @return 异步任务对象
*/
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))); //可根据需需求自定义任务,即可自行包装任务,通过覆写decorateTask方法即可
delayedExecute(t); //将任务放到队列中
return t;
}
/**
* 延迟指定的时间后执行任务
* @param command 任务
* @param delay 延迟时间
* @param unit 时间单位
* @return 异步任务对象
*/
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;
}
/**
* 以固定的频率重复执行任务,注重频率
* 下一个任务的执行时间为上一个任务开始执行的时间加上period
* 注意一下, 延迟initialDelay时间后开始执行任务,而后续的任务将以固有频率(period)执行
* 若下一个任务的执行时间到了,但上一个任务还未执行完毕,则当上一个任务执行完毕后下一个任务立即被执行
* @param command 任务
* @param initialDelay 延迟时间
* @param period 固有频率
* @param unit 时间单位
* @return 异步对象
*/
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;
}
/**
* 以固定的周期重复执行任务,注重周期
* 下一个任务的执行时间为上一个任务执行完毕后的时间加上period,所以每个任务的执行时间依赖于上一个任务的执行时间,导致每个任务的执行时间不固定
* 注意一下, 延迟initialDelay时间后开始执行任务,而后续的任务将以固有频率(period)执行
* @param command 任务
* @param initialDelay 延迟时间
* @param period 固有频率
* @param unit 时间单位
* @return 异步对象
*/
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;
}
/**
* 包装任务
* @param runnable 任务
* @param task 包装对象
* @return 包装对象
*/
protected <V> RunnableScheduledFuture<V> decorateTask(Runnable runnable, RunnableScheduledFuture<V> task) {
return task;
}
/**
* 包装任务
* @param runnable 任务
* @param task 包装对象
* @return 包装对象
*/
protected <V> RunnableScheduledFuture<V> decorateTask(Callable<V> callable, RunnableScheduledFuture<V> task) {
return task;
}
/**
* 将任务放入到队列中
* 在第一次检查时线程池处于SHUTDOWN状态时,则直接拒绝任务
* 在第二次检查时线程池处于SHUTDOWN状态时,则根据属性来决定是否要处理队列中的任务
* @param task 任务
*/
private void delayedExecute(RunnableScheduledFuture<?> task) {
if (isShutdown()) //第一次检查,若此时线程池处于SHUTDOWN状态(或者比SHUTDOWN更大的状态值),则直接拒绝任务,默认情况下会抛出异常
reject(task);
else {
super.getQueue().add(task);
/**
* 走到这里说明任务已经放入队列中
* 第二次检查,若此时线程池处于SHUTDOWN状态,则判断是否是重复执行的任务,在根据其continueExistingPeriodicTasksAfterShutdown/executeExistingDelayedTasksAfterShutdown参数来决定是否要处理队列中的任务
* 默认情况下,针对非重复执行的任务会处理队列中的任务,即canRunInCurrentRunState返回true,最终将不会移除任务
* 默认情况下,针对重复执行的任务将不会处理队列中的任务,即canRunInCurrentRunState返回false,最终将移除任务
*/
if (isShutdown() && !canRunInCurrentRunState(task.isPeriodic()) && remove(task))
task.cancel(false);
else
ensurePrestart();//因为队列中已经存在任务了,为了防止因为线程池处于SHUTDOWN状态导致不存在任何的工作线程去处理队列中的任务,所以这里必须至少要有一个工作线程存在
}
}
/**
* 获取任务的具体执行时间
* @param delay 任务的延迟时间
* @param unit 时间单位
* @return 任务的具体执行时间
*/
private long triggerTime(long delay, TimeUnit unit) {
return triggerTime(unit.toNanos((delay < 0) ? 0 : delay));
}
/**
* 获取任务的具体执行时间,防止任务之间通过compareTo方法比较时溢出
* @param delay 任务的延迟时间
* @return 任务的具体执行时间
*/
long triggerTime(long delay) {
return now() + ((delay < (Long.MAX_VALUE >> 1)) ? delay : overflowFree(delay));
}
/**
* 约束队列中所有任务的延迟时间在Long.MAX_VALUE之内,避免任务之间通过compareTo方法比较时溢出,简单来说就是防止任务的排序出现错误顺序
*
* 假设如果没有调用overflowFree,那么会造成什么后果呢?以下的场景参考自网友的文章:
* 为了方便说明,会采用假设的手法,假设当前时间:100,即:000 0110 0100
*
* 按照注释中的说法应该是下一个任务已经达到了延迟时间,但却因为上一个任务还未结束导致无法执行下一个任务,简单来说就是下一个任务被延误了,所以它的执行时间会小于当前时间,假设是95
* 这个时候又加入了新的任务,而新任务的执行时间设置为Long.MAX_VALUE = 1023,即:0011 1111 1111,在生成任务对象时会调用triggerTime方法计算time,即 100 + 1023 = -925,即:111 1001 1101
* 紧接着调用siftUp方法进行排序,在siftUp中会调用compareTo进行任务之间的比较,在compareTo方法中使用long diff = time - x.time = -925 - 95 = -1020,那么将返回-1,-1说明新任务的延迟时间比队列中任务的延迟时间更短,可是显然不是* 的,正常情况下应该返回1
*
* 若是执行了overflowFree又会怎么样呢?
* long headDelay = 95 - 100 = -5 , 此时的delay = 1023,显然 delay - headDelay = 1023 - (-5) 必然是个负数,就不做计算了,紧接着 delay = 1023 -5 = 1018,同样执行triggerTime计算time,即 1018 + 100 = -930
* 同样执行long diff = time - x.time = -930 - 95 = 1023 > 0,所以会返回1,符合正常的预期(-930:100 0101 1110 -95:111 1010 0001)
*
* 所以,overflowFree方法中把延误的时间给减去,就是为了避免在compareTo方法中出现溢出情况,说实话,这段代码我也看的很痛苦,要不是参考了网友写的文章,我还真看不明白了!
* @param delay 任务的延迟时间
* @return 任务的延迟时间
*/
private long overflowFree(long delay) {
Delayed head = (Delayed) super.getQueue().peek();
if (head != null) {
long headDelay = head.getDelay(NANOSECONDS);
if (headDelay < 0 && (delay - headDelay < 0))
delay = Long.MAX_VALUE + headDelay;
}
return delay;
}
/**
* 获取当前时间,以纳秒为单位
*/
final long now() {
return System.nanoTime();
}
//以异步的形式包装任务
private class ScheduledFutureTask<V> extends FutureTask<V> implements RunnableScheduledFuture<V> {
//任务的序号
private final long sequenceNumber;
//执行任务的具体时间,以纳秒为单位
private long time;
/**
* 定义重复执行任务的周期或延迟时间
*
* 1. 正数表示执行任务的周期,主要针对scheduleAtFixedRate方法,注意一下该方法是在上一个任务开始执行的时间加上周期来决定下一个任务的开始执行时间,所以有可能出现下一个任务已经到达了开始执行的时间,而上一个任务还未执行完毕
* 这个时候说明下一个任务会被延迟执行了,不过绝不会出现下一个任务与上一个任务重叠执行,会等上一个任务执行后才执行
* 2. 负数表示执行任务的延迟时间,主要针对scheduleWithFixedDelay方法,注意一下该方法是在上一个任务执行完毕后延迟多少时间后才开始执行下一个任务
* 3. 0表示不会重复执行任务
*/
private final long period;
/**
* 表示当前任务对象,用于重复执行任务,当上一个任务执行完毕后,更新指定属性并重新将该对象重新放入到队列中
*/
RunnableScheduledFuture<V> outerTask = this;
/**
* 设置索引,用于快速取消
*/
int heapIndex;
/**
* 初始化,设置任务、执行任务的结果、第一次执行任务的延迟时间
* @param r 任务
* @param result 结果值
* @param ns 延迟时间,以毫秒为单位
*/
ScheduledFutureTask(Runnable r, V result, long ns) {
super(r, result);
this.time = ns;
this.period = 0;
this.sequenceNumber = sequencer.getAndIncrement();
}
/**
* 初始化,设置任务、执行任务的结果、第一次执行任务的延迟时间、任务的执行周期
* @param r 任务
* @param result 结果值
* @param ns 延迟时间,以毫秒为单位
* @param period 任务的执行周期
*/
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();
}
/**
* 获取下次执行任务的时间与当前时间的间隔
* @param unit 时间格式
*/
public long getDelay(TimeUnit unit) {
return unit.convert(time - now(), NANOSECONDS); //将指定时间转换成指定格式的时间
}
/**
* 将任务进行排序,以便将执行时间更小的任务排到队列的前头
* @param other 另外一个任务
* @return 结果值
*/
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;
}
/**
* 获取当前任务是否重复执行,不管是周期性还是延迟执行时间,对应了scheduleAtFixedRate与scheduleWithFixedDelay方法
* @return 当前任务是否重复执行,true表示是重复执行
*/
public boolean isPeriodic() {
return period != 0;
}
/**
* 为重复执行的任务设置下次执行的具体时间,该方法只会在上一个任务执行完毕后调用,只有是重复执行的任务才会调用该方法
*/
private void setNextRunTime() {
long p = period;
if (p > 0) //正数表示重复执行的周期
time += p;
else //负数表示重复执行的延迟时间,等到上一个任务执行完毕后延迟指定时间后才开始执行下一个任务
time = triggerTime(-p); //原本p是个负数,-p就变成了正数
}
/**
* 是否成功取消任务
* 1. 若任务还未执行,任务的状态为NEW,调用该方法时,任务的状态将根据mayInterruptIfRunning值变更成INTERRUPTING/CANCELLED
* 因为任务还未运行,所以mayInterruptIfRunning的值应该为false,所以最终super.cancel返回true
* 2. 若任务正在运行中,任务的状态还是为NEW,调用该方法时,任务的状态将根据mayInterruptIfRunning值变更成INTERRUPTING/CANCELLED
* 由于任务正在运行,所以mayInterruptIfRunning的值有可能为true,也有可能为false
* 21. 若mayInterruptIfRunning = true,任务的状态将变成INTERRUPTING,同时中断正在运行任务的线程,此时要注意一下,要看下线程是否响应了中断,比如出现wait、join、slepp、I/O阻塞才会响应中断而抛出中断异常,其他情况
* 下只是简单地设置了中断状态,并未抛出异常,最终又将任务的状态变成INTERRUPTED,同时返回true,可实际上任务还是如实完成了
* 22. 若mayInterruptIfRunning = false,任务的状态将变成CANCELLED,同时返回true,可实际上任务还是如实完成了
* 3. 若任务已执行完成了,自然是无非取消的
*
* 综上所述:只有两种情况下任务会被成功取消/中断
* 1. 任务还未开始执行可以成功取消
* 2. 运行中的任务在响应中断的情况下会抛出异常,这个时候可根据业务来抉择是否取消继续运行
* 3. 其他情况下虽然会返回true,但任务还是会如是地运行
*
* @param mayInterruptIfRunning 是否中断正在运行任务的线程
* @return 是否成功取消任务,此结果值无法决定任务是否真正被取消了
*/
public boolean cancel(boolean mayInterruptIfRunning) {
boolean cancelled = super.cancel(mayInterruptIfRunning);
if (cancelled && removeOnCancel && heapIndex >= 0) //即使返回cancelled = true,任务还是有可能如实地运行,此时的队列移除更多的是针对未开始执行的任务
remove(this);
return cancelled;
}
/**
* Overrides FutureTask version so as to reset/requeue if periodic.
* 执行任务
* 针对非重复执行的任务来说,直接执行即可
* 针对重复执行的任务来说,在执行完任务后,又将该任务放入到队列中
*/
public void run() {
boolean periodic = isPeriodic(); //是否是重复执行的任务
if (!canRunInCurrentRunState(periodic)) //查看当前线程池的状态是否允许运行任务
cancel(false);
else if (!periodic) //若不是重复执行的任务,则直接运行任务
ScheduledFutureTask.super.run();
/**
* 在runAndReset方法中运行任务,若任务运行失败了或被取消了,则返回false,导致周期性运行任务不在执行
* FutureTask#runAndReset中并未设置任务的结果
* 注意一下,将runner设置null是为了防止周期任务使用同一个线程,按道理来说,一个任务执行完毕了,该线程的任务也就即使了,等到下一个任务开始执行时,又会是另外一个线程
* 从outerTask变量我们可以看出它保存了任务的信息,不管是对于上一个任务还是下一个任务来说都是同一个对象,所以我们有必要去更新runner变量的引用
*/
else if (ScheduledFutureTask.super.runAndReset()) {
setNextRunTime(); //下一个执行的具体时间
reExecutePeriodic(outerTask); //将重复执行的任务又放入到队列中等待下次执行
}
}
}
/**
* 将重复执行的任务放入到队列中
* 注意一下,该方法只会被重复执行的任务所调用
* 该方法与delayedExecute作用类似,在线程池处于SHUTDOWN状态下,添加任务到队列时会被直接拒绝,这很符合ThreadPoolExecutor的特性,而该方法添加任务时会根据参数来决定是否入队列,毕竟它已经执行过一次了
* 所以ScheduledThreadPoolExecutor与ThreadPoolExecutor在这点的策略上会有所不同
* @param task 重复执行的任务
*/
void reExecutePeriodic(RunnableScheduledFuture<?> task) {
//双重检查相当于在说明两件事,放不放到队列中是一回事,处不处理队列中的任务又是一个回事
if (canRunInCurrentRunState(true)) { //第一次检查,若此时线程池处于SHUTDOWN状态,则根据continueExistingPeriodicTasksAfterShutdown参数来决定是否继续执行任务
super.getQueue().add(task);
if (!canRunInCurrentRunState(true) && remove(task)) //在第二次检查,走到这里说明任务已经放入到队列中了,此时若线程池处于SHUNTDOWN状态,则判断是否要处理队列中的任务,若不处理则移除
task.cancel(false);
else
ensurePrestart(); //因为队列中已经存在任务了,为了防止因为线程池处于SHUTDOWN状态导致不存在任何的工作线程去处理队列中的任务,所以这里必须至少要有一个工作线程存在
}
}
/**
* 根据当前线程池的状态决定是否允许执行任务
* @param periodic 当前任务是否属于重复执行任务
* @return 是否允许执行任务
*/
boolean canRunInCurrentRunState(boolean periodic) {
return isRunningOrShutdown(periodic ? continueExistingPeriodicTasksAfterShutdown : executeExistingDelayedTasksAfterShutdown);
}
/**
* 关闭线程池时清理队列中不执行的任务
* 该方法是在关闭线程池时被调用,参看ThreadPoolExecutor#shutdown
*/
@Override
void onShutdown() {
BlockingQueue<Runnable> q = super.getQueue();
boolean keepDelayed = getExecuteExistingDelayedTasksAfterShutdownPolicy(); //当线程池处于SHUTDOWN状态时,是否继续执行非周期性任务
boolean keepPeriodic = getContinueExistingPeriodicTasksAfterShutdownPolicy();//当线程池处于SHUTDOWN状态时,是否继续执行周期性任务
if (!keepDelayed && !keepPeriodic) { //若两个都是false,则清空队列并更改任务的状态
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()) { // also remove if already cancelled
if (q.remove(t))
t.cancel(false);
}
}
}
}
tryTerminate(); //尝试去终止线程池
}
/**
* 立即执行任务,返回异步对象
* @param task 任务
* @return 异步对象
*/
public Future<?> submit(Runnable task) {
return schedule(task, 0, NANOSECONDS);
}
/**
* 立即执行任务,返回异常对象,可获取任务的返回值
* @param task 任务
* @return 异步对象
*/
public <T> Future<T> submit(Runnable task, T result) {
return schedule(Executors.callable(task, result), 0, NANOSECONDS);
}
/**
* 立即执行任务,返回异步对象
* @param task 任务
* @return 异步对象
*/
public <T> Future<T> submit(Callable<T> task) {
return schedule(task, 0, NANOSECONDS);
}
/**
* 当关闭线程池时,设置是否继续执行周期性任务的属性
* 若是立即关闭线程池(shutdownNow)或此属性设置成false,则周期性任务不会被执行
* @param value 关闭线程池时,是否继续执行周期性任务
*/
public void setContinueExistingPeriodicTasksAfterShutdownPolicy(boolean value) {
continueExistingPeriodicTasksAfterShutdown = value;
if (!value && isShutdown())
onShutdown();
}
/**
* 获取当关闭线程时是否继续执行周期性任务的属性值,默认情况下是false
* @return 属性值
*/
public boolean getContinueExistingPeriodicTasksAfterShutdownPolicy() {
return continueExistingPeriodicTasksAfterShutdown;
}
/**
* 当关闭线程池时,设置是否继续执行非周期性任务(延迟任务)的属性
* 若是立即关闭线程池(shutdownNow)或此属性设置成false,则非周期性任务不会被执行
* @param value 关闭线程池时,是否继续执行非周期性任务
*/
public void setExecuteExistingDelayedTasksAfterShutdownPolicy(boolean value) {
executeExistingDelayedTasksAfterShutdown = value;
if (!value && isShutdown())
onShutdown();
}
/**
* 获取当关闭线程时是否继续执行非周期性任务的属性值,默认情况下是true
* @return 属性值
*/
public boolean getExecuteExistingDelayedTasksAfterShutdownPolicy() {
return executeExistingDelayedTasksAfterShutdown;
}
/**
* 当任务被取消时,设置是否立即移除该任务
* @param value 当任务被取消时,是否立即移除任务
*/
public void setRemoveOnCancelPolicy(boolean value) {
removeOnCancel = value;
}
/**
* 获取当任务被取消时,是否立即移除该任务,默认情况是false,建议设置成true
* @return 属性值
*/
public boolean getRemoveOnCancelPolicy() {
return removeOnCancel;
}
/**
* 该队列是基于二叉堆的数据结构,堆中分为最大堆与最小堆,它采用了最小堆,可参看之前的文章http://zlia.tech/2020/01/10/explain-binary-heap
* 该队列会将执行时间较小的任务排列到前头,但是注意一下它只保证父节点的执行时间比子节点的执行时间小,而子节点之间的关系并不一定是有序的
* 若对堆不熟悉或者不认识的读者最好先去了解下,因为本章不会详细介绍二叉堆的知识点
*/
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-Follower模型的变体,何为Leader-Follower自行搜索即可
* 最小化不必要的等待时间
* 若当前线程是个Leader(一种标识),始终都只有一个Leader线程,Leader线程只会等待任务执行的延迟时间,而其他的Follower线程处于一直等待状态,这就是所谓的Leader-Follower模型的变体
* Leader线程在return之前必须去唤醒其他的线程,因为其他线程都处于等待状态下
*/
private Thread leader = null;
/**
* 条件锁
* 添加新任务时或线程成为Leader线程时,该锁会被唤醒
*/
private final Condition available = lock.newCondition();
/**
* 设置索引
* heapIndex用于快速取消,当取消一个任务时,该任务的索引会被设置成-1,且该索引处变成了null,如果在cancel方法中不加上heapIndex的判断,remove依然会走完
*/
private void setIndex(RunnableScheduledFuture<?> f, int idx) {
if (f instanceof ScheduledFutureTask)
((ScheduledFutureTask)f).heapIndex = idx;
}
/**
* 上浮插入的节点以满足二叉堆的性质
* 二叉堆的性质:ScheduledThreadPoolExecutor#DelayWorkQueue中采用的是最小堆,即父节点的时间比子节点的时间要小
*
* 插入的节点的操作步骤一般如下:
* 1. 将插入的节点放到数组的末尾
* 2. 由于需要父节点的时间比子节点的时间要小,故要与父节点进行比较
* 3. 若插入的节点比父节点小,则将插入的节点与父节点进行值交换,在交换后它又有了新的父节点,故而需要继续往上比较,直到到底堆顶或不在小于父节点,相当于在重复步骤2
* 4. 若插入的节点比父节点大,直接结束
*
* 假设一个节点的索引为:N,其父节点的索引为:(N - 1)/2,左子节点的索引为:2N + 1,其右子节点的索引为:2N + 2
* @param k 插入的节点的索引
* @param key 插入的节点
*/
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;
/**
* 走到这里说明插入节点的值小于父节点的值,不过它不是急于将插入节点的值直接放入到父节点的位置上,因为即使在交换后它仍然还有父节点,还需要在往上进行比较
* 所以能确定的是将父节点放到插入节点的位置上,而对于插入的节点等最终比较完毕了在放入,即while循环后续的代码
*/
queue[k] = e;
setIndex(e, k);
k = parent;
}
queue[k] = key;
setIndex(key, k);
}
/**
* 下沉节点以满足二叉堆的性质
* 思路:
* - 不管移除的是哪个节点,拿数组末尾的节点是最少成本的,因为拿该节点的值去覆盖移除节点的值来使其还是一颗完全二叉树,所以最终只要让其满足二叉堆性质就可以了
* - 针对移除节点来说,就相当于把移除节点的位置空出来了,因为是二叉堆要满足其性质,所以就要考虑是它的子节点还是末尾的节点更适合来做移除节点的位置(最大堆/最小堆)
* - 因为末尾元素最终都会被移动到指定位置,故而先将末尾位置置null
*
* 针对要移除的节点分为两种情况(最小堆):
* 1. 移除的节点无子节点,也就是说是叶子节点,如下步骤:
* 11. 直接将末尾的节点的值覆盖,同时将末尾位置设置成null,因为它并无节点,所以不用考虑是否比子节点大,但是有一点要考虑是覆盖完后是否比父节点还要小
* 12. 若比父节点还要小的话就要做上浮操作,即调用siftUp即可,最后退出
*
* 2. 删除的节点有子节点,如下步骤:
* 21. 先比较两个子节点的值的大小,获取值最小的节点
* 22. 在将值最小的节点与末尾的节点进行比较(若只有一个子节点的话,那么只能是左子节点,就直接比较大小)
* 23. 若是末尾的节点更小的话,那么直接覆盖移除的节点的值即可,同时将末尾位置设置成null,最后退出
* 24. 若是其子节点更小的话,那么用其值最小的节点覆盖到移除的节点的值,此时值最小的节点的位置就空出来了(相当于此时它被移除了),那么此时重复步骤12
*
* 假设一个节点的索引为:N,其父节点的索引为:(N - 1)/2,左子节点的索引为:2N + 1,其右子节点的索引为:2N + 2
* @param k 移除节点的索引
* @param key 末尾节点
*/
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;//移除节点的右子节点的索引
if (right < size && c.compareTo(queue[right]) > 0) //先判断是否存在右子节点,若左右子节点都存在,那么要找出值最小的节点
c = queue[child = right];
if (key.compareTo(c) <= 0) //末尾节点与值最小的节点进行比较
break;
queue[k] = c; //这里的操作与siftUp同理
setIndex(c, k);
k = child;
}
queue[k] = key;
setIndex(key, k);
}
/**
* 队列以50%的速率进行扩容
*/
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);
}
/**
* 获取队列中指定任务的索引
* 若不存在指定任务则返回-1
* @param x 指定任务
* @return 指定任务的索引
*/
private int indexOf(Object x) {
if (x != null) {
if (x instanceof ScheduledFutureTask) {
int i = ((ScheduledFutureTask) x).heapIndex;
// Sanity check; x could conceivably be a
// ScheduledFutureTask from some other pool.
if (i >= 0 && i < size && queue[i] == x)
return i;
} else {
for (int i = 0; i < size; i++)
if (x.equals(queue[i]))
return i;
}
}
return -1;
}
/**
* 获取队列中是否包含指定任务
* @param x 指定任务
* @return 是否包含指定任务
*/
public boolean contains(Object x) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return indexOf(x) != -1;
} finally {
lock.unlock();
}
}
/**
* 获取队列中是否成功移除指定任务
* @param x 指定任务
* @return 结果值
*/
public boolean remove(Object x) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
int i = indexOf(x);
if (i < 0)
return false;
setIndex(queue[i], -1);
int s = --size;
RunnableScheduledFuture<?> replacement = queue[s]; //获取末尾的节点
queue[s] = null; //预先将末尾的节点置null,因为末尾的节点会移动到其他位置上
if (s != i) { //若为true,说明移除的节点不是末尾节点
siftDown(i, replacement);//下沉只是以移除节点为起始段往下开始比较,但它的上部分并未比较,所以有可能出现移动完毕的节点比父节点的值还大
if (queue[i] == replacement) //走到这里说明下沉结束,表示末尾的节点已经移动完成,移动后有可能出现末尾节点的值比父节点的值还小,针对siftDown方法注释中的11-12点
siftUp(i, replacement);
}
return true;
} finally {
lock.unlock();
}
}
/**
* 获取队列中的第一个元素
* @return 结果值
*/
public RunnableScheduledFuture<?> peek() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return queue[0];
} finally {
lock.unlock();
}
}
/**
* 队列中插入指定任务
* @param x 指定任务
* @return 结果值
*/
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) {
/**
* 这里需要注意下!!!
* 走到这里表明队列中的堆顶节点已经换成插入的节点,简单来说就是队列更新了,在这个过程中有可能已经在执行take方法中的 available.awaitNanos(delay) 语句,那么此时的等待时间已经没有意义了
* 所以执行了available.signal(个人觉得还应该去判断是否堆顶节点改变了,但不一定是插入的节点成为了新的堆顶节点),因为队列的堆顶节点都更新了,需要重新获取并等待,由于可能存在多个线程,故将leader设置成null,让线程* 之间去竞争谁成为Leader线程
*/
leader = null;
available.signal();
}
} finally {
lock.unlock();
}
return true;
}
/**
* 移除堆顶节点
* 执行该方法说明已经到达任务的执行时间,需要将该节点从队列中移除掉,就相当于了移除掉了堆顶节点,按照siftDown的思路我们知道拿末尾节点是最小成本的
* 在remove方法中还调用了siftUp,但由于是这里是从堆顶开始比较,所以无需在往上比较了
* @param f 堆顶节点
* @return 堆顶节点
*/
private RunnableScheduledFuture<?> finishPoll(RunnableScheduledFuture<?> f) {
int s = --size;
RunnableScheduledFuture<?> x = queue[s];
queue[s] = null;
if (s != 0)
siftDown(0, x);
setIndex(f, -1);
return f;
}
/**
* 立即获取可执行的堆顶节点
* 可执行表示已到达执行任务的时间,若还未到达则返回null
* @return 堆顶节点
*/
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();
}
}
/**
* 获取可执行的堆顶节点
* 可执行表示已到达执行任务的时间,若未到达则会进行等待
*
* 这里使用了Leader-Follower模型的变体,简单来说,多个线程会同时去抢夺队列中的堆顶节点,谁抢到了谁就成为Leader线程,而其他线程要一直阻塞着,这些线程成为Follower线程
* 1. 假设A线程抢到了,那么最终会执行到 available.awaitNanos(delay) 等待一定的时间,注意一下,该方法是会释放锁的!!!当初也是因为没注意到该点也花费了很多时间
* 2. 既然释放了锁,那么其他Follower线程也就可以进行来了,陆续执行到 available.await() 语句上进行阻塞
* 3. 想象一下,如果不对其他线程进行阻塞,同样执行到 available.awaitNanos(delay) 语句,A线程已经执行完毕了早就返回了,而你这个时候的等待时间就相当于是白费了,所以它直接采用了一直等待的策略
* 3. 等待Leader线程执行完毕后才会唤醒其中一个线程,这个时候唤醒的线程就变成了Leader线程
* 4. 所以说Leader-Follower模型的变体是始终都只有一个Leader线程,多个Follower线程
* 总结:Leader-Follower模型的变体减少了不必要的等待时间
*
* @return 可执行的堆顶节点
*/
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);//Leader线程阻塞一定的延迟时间,注意该方法会释放锁
} finally {
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
if (leader == null && queue[0] != null) //始终都会有一个Leader线程
available.signal();
lock.unlock();
}
}
/**
* 获取可执行的堆顶节点
* 可执行表示已到达执行任务的时间,若在指定时间内还未到达则返回null,所以等待时间最好大于延迟时间
*
* 1. 若nanos < delay 最终会返回null,需要多次调用poll才能获取到可执行的任务
* 2. 若nanos > delay 最终只会等待延迟时间后就返回
* 3. 当Leader != null时,说明已经有Leader线程在处理堆顶节点了,其他线程等着就是了
* @param timeout 等待时间
* @param unit 时间单位
* @return 可执行的堆顶节点或null
*/
public RunnableScheduledFuture<?> poll(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
for (;;) {
RunnableScheduledFuture<?> first = queue[0];
if (first == null) {
if (nanos <= 0)
return null;
else
nanos = available.awaitNanos(nanos);
} else {
long delay = first.getDelay(NANOSECONDS);
if (delay <= 0)
return finishPoll(first);//走到这里说明是可执行的任务
if (nanos <= 0)
return null;
first = null; // don't retain ref while waiting
if (nanos < delay || leader != null) //若nanos < delay 最终会返回null,需要多次调用poll才能获取到可执行的任务;若leader != null,说明有Leader线程获取了堆顶节点,其他线程等着就是了
nanos = available.awaitNanos(nanos);
else {
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
long timeLeft = available.awaitNanos(delay); // 该方法返回的是剩余等待时间
nanos -= delay - timeLeft;//delay - timeLeft 表示已经等待了多少时间,nanos表示一共要等待多长时间,所以最终的结果是在计算剩余的等待时间
} finally {
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
if (leader == null && queue[0] != null)
available.signal();
lock.unlock();
}
}
/**
* 清空队列
*/
public void clear() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
for (int i = 0; i < size; i++) {
RunnableScheduledFuture<?> t = queue[i];
if (t != null) {
queue[i] = null;
setIndex(t, -1);
}
}
size = 0;
} finally {
lock.unlock();
}
}
/**
* 获取可执行的堆顶节点
* @return 可执行的堆顶节点或null
*/
private RunnableScheduledFuture<?> peekExpired() {
// assert lock.isHeldByCurrentThread();
RunnableScheduledFuture<?> first = queue[0];
return (first == null || first.getDelay(NANOSECONDS) > 0) ?
null : first;
}
/**
* 清除可执行的所有节点,并将这些节点放入集合中
* 该方法会在调用ThreadPoolExecutor#shutdownNow时被调用,实际上最终队列中的任务都会被清除
* @param c 集合
* @return 可执行节点的个数
*/
public int drainTo(Collection<? super Runnable> c) {
if (c == null)
throw new NullPointerException();
if (c == this)
throw new IllegalArgumentException();
final ReentrantLock lock = this.lock;
lock.lock();
try {
RunnableScheduledFuture<?> first;
int n = 0;
while ((first = peekExpired()) != null) {
c.add(first); // In this order, in case add() throws.
finishPoll(first);
++n;
}
return n;
} finally {
lock.unlock();
}
}
/**
* 清除可执行节点,并将这些节点放入集合中,与上面的方法相比控制了清除节点的数量
* @param c 集合
* @return 可执行节点的个数
*/
public int drainTo(Collection<? super Runnable> c, int maxElements) {
if (c == null)
throw new NullPointerException();
if (c == this)
throw new IllegalArgumentException();
if (maxElements <= 0)
return 0;
final ReentrantLock lock = this.lock;
lock.lock();
try {
RunnableScheduledFuture<?> first;
int n = 0;
while (n < maxElements && (first = peekExpired()) != null) {
c.add(first); // In this order, in case add() throws.
finishPoll(first);
++n;
}
return n;
} finally {
lock.unlock();
}
}
//利用迭代器可获取队列中的任务,这里就不贴了...
}
总结
-
当线程池处于SHUTDOWN时,是否继续执行队列中的任务,包括重复执行的任务与延迟的任务,针对这两者取决于continueExistingPeriodicTasksAfterShutdown与executeExistingDelayedTasksAfterShutdown属性。
-
重复执行任务的原理:任务对象中有一个outerTask属性指向自身,在上一个任务执行完毕后会更新下一个任务的执行时间,并将下一个任务再次放入到队列中(上一个任务在执行前就已经从队列中移除了),重复此操作。
-
scheduleAtFixedRate:按照一定的频率去执行任务,下一个任务的执行时间从上一个任务开始执行的时间开始算起,所以有可能导致已经到达下一个任务的执行了,但上一个任务还未执行结束的情况,直到上一个任务执行结束了,下一个任务立即被执行。
-
scheduleWithFixedDelay:按照一定的周期去执行任务,下一个任务的执行时间从上一个任务执行结束后的时间开始算起,所以下一个任务的执行时间严重依赖于上一个任务。
-
removeOnCancel:该属性表明当取消队列中的任务时是否立即将任务从队列中移除,建议设置成true,而默认情况下是false,这会造成时间成本上的浪费,简单来说,被取消的任务会更改其状态,除此之外,它仍会像其他任务一样被获取然后等待一定的延迟时间后,直到可执行了,那么此时去执行时发现它的状态不对,就只能不了了之了,要是延迟时间在长点...,不可想象了!
-
DelayedWorkQueue队列采用二叉堆(最小堆)数据结构,其中使用了Leader-Follower模型的变体,始终只有一个Leader线程在处理队列中的堆顶节点,其他Follower线程只能等待着。
-
老版本的JDK中通常使用Timer,但其实该类存在一些
缺陷:通过单线程来执行所有的任务,若其中一个任务执行时间过长,将会导致其他任务无法执行或者无法准时执行;若其中一个任务抛出异常,其他的任务将不会再运行;Timer的执行时间依赖于系统时间,也就是说针对不同的平台,比如Window、Linux平台下可能会有不同的行为,而ScheduledThreadPool中的now采用的System.nanoTime,该方法不关联系统时间,它代表了从某个固定时间点到现在所经过的毫秒数,该计算方式跟JVM有关系,不同的JVM可能固定时间点不同,对于不同的平台使用的JVM肯定是同一个,所以不管再哪个平台下任务的行为都是一样的。
结束语
知识点过多,建议当作好几篇文章分开看,笔者断断续续花了应该有2个月时间,若有错误欢迎指出!
参考资料
《Java编程思想》
浙公网安备 33010602011771号