六. 线程池与Executor 框架
- 六. 线程池与Executor 框架
- 1. 使用线程池的好处
- 2. Executor 框架
- 3. ThreadPoolExecutor详解
- 4. ScheduledThreadPoolExecutor详解
- 5. 各种线程池的适用场景介绍
六. 线程池与Executor 框架

1. 使用线程池的好处
- 线程池提供了一种限制和管理资源(包括执行一个任务)。 每个线程池还维护一些基本统计信息
使用线程池的好处
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
- 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
2. Executor 框架
2.1 Executor 简介
Executor 框架是Java5之后引进的,在Java 5之后,通过 Executor 来启动线程比使用 Thread 的 start 方法更好,除了更易管理,效率更好(用线程池实现,节约开销)外,还有关键的一点:有助于避免 this 逃逸问题。
this逃逸是指在构造函数返回之前其他线程就持有该对象的引用. 调用尚未构造完全的对象的方法可能引发令人疑惑的错误。
2.2 Executor 框架结构(主要由三大部分组成)
1 任务
执行任务需要实现的Runnable接口或Callable接口。Runnable接口或Callable接口实现类都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行。
两者的区别:
- Runnable接口不会返回结果,但是Callable接口可以返回结果。后面介绍Executors类的一些方法的时候会介绍到两者的相互转换。
2 任务的执行
- 包括任务执行机制的核心接口Executor ,以及继承自Executor 接口的ExecutorService接口。
- ScheduledThreadPoolExecutor和ThreadPoolExecutor这两个关键类实现了ExecutorService接口。
3 异步计算的结果
- Future接口以及Future接口的实现类FutureTask类。
- 当我们把Runnable接口或Callable接口的实现类提交(调用submit方法)给ThreadPoolExecutor或ScheduledThreadPoolExecutor时,会返回一个FutureTask对象。
2.3 Executor 框架的使用示意图
-
主线程首先要创建实现Runnable或者Callable接口的任务对象。
备注: 工具类Executors可以实现Runnable对象和Callable对象之间的相互转换。(Executors.callable(Runnable task)或Executors.callable(Runnable task,Object resule))
-
然后可以把创建完成的Runnable对象直接交给ExecutorService执行(ExecutorService.execute(Runnable command));或者也可以把Runnable对象或Callable对象提交给ExecutorService执行(ExecutorService.submit(Runnable task)或ExecutorService.submit(Callable task))
执行execute()方法和submit()方法的区别是什么呢?
1)execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;
2)submit()方法用于提交需要返回值的任务。线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完 -
如果执行ExecutorService.submit(…),ExecutorService将返回一个实现Future接口的对象(我们刚刚也提到过了执行execute()方法和submit()方法的区别,到目前为止的JDK中,返回的是FutureTask对象)。由于FutureTask实现了Runnable,程序员也可以创建FutureTask,然后直接交给ExecutorService执行。
-
最后,主线程可以执行FutureTask.get()方法来等待任务执行完成。主线程也可以执行FutureTask.cancel(boolean mayInterruptIfRunning)来取消此任务的执行。
3. ThreadPoolExecutor详解
线程池实现类ThreadPoolExecutor是Executor 框架最核心的类
3.1 ThreadPoolExecutor类的四个比较重要的属性

3.2 ThreadPoolExecutor类中提供的四个构造方法
/**
* 用给定的初始参数创建一个新的ThreadPoolExecutor。
* @param corePoolSize 核心线程池大小
* @param maximumPoolSize 最大线程池的大小
* @param keepAliveTime 当线程池中的线程数量大于corePoolSize的时候,如果这时没有新的任务提交,
*核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了keepAliveTime;
* @param unit keepAliveTime参数的时间单位
* @param workQueue 等待队列,当任务提交时,如果线程池中的线程数量大于等于corePoolSize的时候,把该任务封装成一个Worker对象放入等待队列;
*
* @param threadFactory 执行者创建新线程时使用的工厂
* @param handler RejectedExecutionHandler类型的变量,表示线程池的饱和策略。
* 如果阻塞队列满了并且没有空闲的线程,这时如果继续提交任务,就需要采取一种策略处理该任务。
* 线程池提供了4种策略:
1.AbortPolicy:直接抛出异常,这是默认策略;
2.CallerRunsPolicy:用调用者所在的线程来执行任务;
3.DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
4.DiscardPolicy:直接丢弃任务;
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue 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.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
3.3 如何创建ThreadPoolExecutor
在《阿里巴巴Java开发手册》"并发处理"这一章节,明确指出线程资源必须通过线程池提供,不允许在应用中自行显示创建线程。
为什么呢?
使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源开销,解决资源不足的问题。如果不使用线程池,有可能会造成系统创建大量同类线程而导致消耗完内存或者"过度切换"的问题。
另外《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险
Executors 返回线程池对象的弊端如下:
- FixedThreadPool 和 SingleThreadExecutor : 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致OOM。
- CachedThreadPool 和 ScheduledThreadPool : 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致OOM。
方式一:通过构造方法实现
方式二:通过Executor 框架的工具类Executors来实现
- 我们可以创建三种类型的ThreadPoolExecutor:
FixedThreadPool
SingleThreadExecutor
CachedThreadPool
底层都是调用的ThreadPoolExecutor的构造方法
3.4 FixedThreadPool详解
FixedThreadPool被称为可重用固定线程数的线程池。
/**
* 创建一个可重用固定数量线程的线程池
*在任何时候至多有n个线程处于活动状态
*如果在所有线程处于活动状态时提交其他任务,则它们将在队列中等待,
*直到线程可用。 如果任何线程在关闭之前的执行期间由于失败而终止,
*如果需要执行后续任务,则一个新的线程将取代它。池中的线程将一直存在
*知道调用shutdown方法
* @param nThreads 线程池中的线程数
* @param threadFactory 创建新线程时使用的factory
* @return 新创建的线程池
* @throws NullPointerException 如果threadFactory为null
* @throws IllegalArgumentException if {@code nThreads <= 0}
*/
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue(),
threadFactory);
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
新创建的FixedThreadPool的corePoolSize和maximumPoolSize都被设置为nThreads。
FixedThreadPool的execute()方法运行示意图
上图说明:
- 如果当前运行的线程数小于corePoolSize,则创建新的线程来执行任务;
- 当前运行的线程数等于corePoolSize后,将任务加入LinkedBlockingQueue;
- 线程执行完1中的任务后,会在循环中反复从LinkedBlockingQueue中获取任务来执行;
FixedThreadPool使用无界队列 LinkedBlockingQueue(队列的容量为Intger.MAX_VALUE)作为线程池的工作队列会对线程池带来如下影响:
- 当线程池中的线程数达到corePoolSize后,新任务将在无界队列中等待,因此线程池中的线程数不会超过corePoolSize;
- 由于1,使用无界队列时maximumPoolSize将是一个无效参数;
- 由于1和2,使用无界队列时keepAliveTime将是一个无效参数;
- 运行中的FixedThreadPool(未执行shutdown()或shutdownNow()方法)不会拒绝任务
3.5 SingleThreadExecutor详解
SingleThreadExecutor是使用单个worker线程的Executor。
/**
*创建使用单个worker线程运行无界队列的Executor
*并使用提供的ThreadFactory在需要时创建新线程
*
* @param threadFactory 创建新线程时使用的factory
*
* @return 新创建的单线程Executor
* @throws NullPointerException 如果ThreadFactory为空
*/
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue(),
threadFactory));
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()));
}
新创建的SingleThreadExecutor的corePoolSize和maximumPoolSize都被设置为1.
其他参数和FixedThreadPool相同。SingleThreadExecutor使用无界队列LinkedBlockingQueue作为线程池的工作队列(队列的容量为Intger.MAX_VALUE)
SingleThreadExecutor使用无界队列作为线程池的工作队列会对线程池带来的影响与FixedThreadPool相同。
SingleThreadExecutor的运行示意图
上图说明:
- 如果当前运行的线程数少于corePoolSize,则创建一个新的线程执行任务;
- 当前线程池中有一个运行的线程后,将任务加入LinkedBlockingQueue
- 线程执行完1中的任务后,会在循环中反复从LinkedBlockingQueue中获取任务来执行;
3.6 CachedThreadPool详解
CachedThreadPool是一个会根据需要创建新线程的线程池。
/**
* 创建一个线程池,根据需要创建新线程,但会在先前构建的线程可用时重用它,
*并在需要时使用提供的ThreadFactory创建新线程。
* @param threadFactory 创建新线程使用的factory
* @return 新创建的线程池
* @throws NullPointerException 如果threadFactory为空
*/
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue(),
threadFactory);
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue());
}
CachedThreadPool的corePoolSize被设置为空(0),maximumPoolSize被设置为Integer.MAX.VALUE,即它是无界的,这也就意味着如果主线程提交任务的速度高于maximumPool中线程处理任务的速度时,CachedThreadPool会不断创建新的线程
极端情况下,这样会导致耗尽cpu和内存资源。
CachedThreadPool的execute()方法的执行示意图
上图说明:
- 首先执行SynchronousQueue.offer(Runnable task)。如果当前maximumPool中有闲线程正在执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS),那么主线程执行offer操作与空闲线程执行的poll操作配对成功,主线程把任务交给空闲线程执行,execute()方法执行完成,否则执行下面的步骤2;
- 当初始maximumPool为空,或者maximumPool中没有空闲线程时,将没有线程执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)。这种情况下,步骤1将失败,此时CachedThreadPool会创建新线程执行任务,execute方法执行完成;
3.7 ThreadPoolExecutor使用示例
首先创建一个Runnable接口的实现类WorkerThread
public class ThreadPoolExecutorDemo {
public static void main(String[] args) {
//创建一个FixedThreadPool对象
ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
//创建WorkerThread对象(WorkerThread类实现了Runnable 接口)
Runnable worker = new WorkerThread("" + i);
//执行Runnable
executor.execute(worker);
}
//终止线程池
executor.shutdown();
while (!executor.isTerminated()) {
}
System.out.println("Finished all threads");
}
}
shutdown()VS shutdownNow()
- shutdown()方法表明关闭已在Executor上调用,因此不会再向DelayedPool添加任何其他任务(由ScheduledThreadPoolExecutor类在内部使用), 但是,已经在队列中提交的任务将被允许完成。
- shutdownNow()方法试图终止当前正在运行的任务,并停止处理排队的任务并返回正在等待执行的List。
isTerminated() Vs isShutdown()
- isShutDown当调用shutdown()方法后返回为true。
- isTerminated当调用shutdown()方法后,并且所有提交的任务完成后返回为true
4. ScheduledThreadPoolExecutor详解
4.1 ScheduledThreadPoolExecutor简介
- ScheduledThreadPoolExecutor主要用来在给定的延迟后运行任务,或者定期执行任务。
- ScheduledThreadPoolExecutor使用的任务队列DelayQueue封装了一个PriorityQueue,PriorityQueue会对队列中的任务进行排序,执行所需时间短的放在前面先被执行(ScheduledFutureTask的time变量小的先执行),如果执行所需时间相同则先提交的任务将被先执行(ScheduledFutureTask的squenceNumber变量小的先执行)。
4.2 ScheduledThreadPoolExecutor运行机制
ScheduledThreadPoolExecutor的执行主要分为两大部分:
- 当调用ScheduledThreadPoolExecutor的 scheduleAtFixedRate() 方法或者scheduleWirhFixedDelay() 方法时,会向ScheduledThreadPoolExecutor的 DelayQueue 添加一个实现了 RunnableScheduledFutur 接口的 ScheduledFutureTask 。
- 线程池中的线程从DelayQueue中获取ScheduledFutureTask,然后执行任务。
ScheduledThreadPoolExecutor为了实现周期性的执行任务,对ThreadPoolExecutor做了如下修改:
- 使用 DelayQueue 作为任务队列;
- 获取任务的方不同
- 执行周期任务后,增加了额外的处理
4.3 ScheduledThreadPoolExecutor执行周期任务的步骤
- 线程1从DelayQueue中获取已到期的ScheduledFutureTask(DelayQueue.take())。到期任务是指ScheduledFutureTask的time大于等于当前系统的时间;
- 线程1执行这个ScheduledFutureTask;
- 线程1修改ScheduledFutureTask的time变量为下次将要被执行的时间;
- 线程1把这个修改time之后的ScheduledFutureTask放回DelayQueue中(DelayQueue.add())。
4.4 ScheduledThreadPoolExecutor使用示例
创建一个简单的实现Runnable接口的类
测试程序使用ScheduledExecutorService和Executors.newScheduledThreadPool实现的java调度
4.4.1 ScheduledExecutorService scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit)方法
//可以使用ScheduledExecutorService scheduleAtFixedRate方法来安排任务在初始延迟后运行,然后在给定的时间段内运行。
scheduledThreadPool.scheduleAtFixedRate(worker, 0, 10, TimeUnit.SECONDS);
4.4.2 ScheduledExecutorService scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit)方法
//ScheduledExecutorService scheduleWithFixedDelay方法可用于以初始延迟启动周期性执行,然后以给定延迟执行。 延迟时间是线程完成执行的时间。
scheduledThreadPool.scheduleWithFixedDelay(worker, 0, 1, TimeUnit.SECONDS);
4.4.3 scheduleWithFixedDelay() vs scheduleAtFixedRate()
- scheduleAtFixedRate(…)将延迟视为两个任务开始之间的差异(即定期调用)
不管是否执行完 - scheduleWithFixedDelay(…)将延迟视为一个任务结束与下一个任务开始之间的差异,执行完才开始下一个。
scheduleAtFixedRate(): 在给定的初始延迟之后创建并执行,随后以给定的时间段首先启用的周期性动作; 那就是执行将在initialDelay之后开始,然后initialDelay+period ,然后是initialDelay + 2 * period ,等等。 如果任务的执行遇到异常,则后续的执行被抑制。 否则,任务将仅通过取消或终止执行人终止。 如果任务执行时间比其周期长,则后续执行可能会迟到,但不会同时执行。
scheduleWithFixedDelay() : 在给定的初始延迟之后创建并执行,首先启用的定期动作,随后在一个执行的终止和下一个执行的开始之间给定的延迟。 如果任务的执行遇到异常,则后续的执行被抑制。 否则,任务将仅通过取消或终止执行终止。
5. 各种线程池的适用场景介绍
FixedThreadPool: 适用于为了满足资源管理需求,而需要限制当前线程数量的应用场景。它适用于负载比较重的服务器;
SingleThreadExecutor: 适用于需要保证顺序地执行各个任务并且在任意时间点,不会有多个线程是活动的应用场景。
CachedThreadPool: 适用于执行很多的短期异步任务的小程序,或者是负载较轻的服务器;
ScheduledThreadPoolExecutor: 适用于需要多个后台执行周期任务,同时为了满足资源管理需求而需要限制后台线程的数量的应用场景,
SingleThreadScheduledExecutor: 适用于需要单个后台线程执行周期任务,同时保证顺序地执行各个任务的应用场景。






