一、背景
多线程的异步执行方式,虽然能够最大限度的发挥多核计算机的计算能力,但是如果不加控制,反而会对系统造成负担:
- 每次使用新线程都需要创建与回收,不断的线程创建与回收,会给系统带来压力,大量的线程回收会给GC带来很大的压力;
- 线程本身也要占用内存,如果不限制线程数量,存在大量的线程会占用内存资源,甚至可能会导致 Out of Memory。
使用线程池:
- 线程复用,避免重复的创建线程与回收线程;
- 线程管理,线程池的大小限制系统可创建的线程数量;
二、线程池 Executor 框架
线程池相关类与接口继承层次:
Executor (interface)
|
|---> ExecutorService (interface)
|
|---> AbstractExecutorService (abstract class)
| |
| |---> ThreadPoolExecutor (class)
| |
| |---> ForkJoinPool (class)
|
|---> ScheduledExecutorService (interface)
|
|---> ScheduledThreadPoolExecutor (extends ThreadPoolExecutor)
2.1 Executor (interface)
线程池框架中最顶层的接口定义
2.1.1 源码介绍
public interface Executor {
void execute(Runnable command);
}
- 只定义了一个方法:execute(Runnable) 所以,只能提交Runnable形式的任务,而不能提交带返回值的Callable任务
2.2 ExecutorService (interface)
所有实现ExecutorService的类都是线程池
2.2.1 源码介绍
public interface ExecutorService extends Executor {
void shutdown();
List<Runnable> shutdownNow();
boolean isShutdown();
boolean isTerminated();
boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException;
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException;
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
- ExecutorService 在 Executor的基础上加入了线程池的生命周期管理;
- ExecutorService 增加了submit方法,支持提交带返回值的 Callable 形式的任务,以及Runnable任务,任务提交完后返回一个Future,它代表一个异步任务执行的结果;
2.2.2 生命周期方法说明
- shutdown 与 shutdownNow:这两个方法作用都是关闭线程池。都是非阻塞的,即调用完成后立即返回,不会等待线程池关闭完成再返回;
- shutdown : 方法会等待线程池中已经运行的任务和阻塞队列中等待执行的任务执行完成;
- shutdownNow :不会等待任务完成,会尝试中断线程池中已经运行的任务,阻塞队列中等待的任务不会再被执行,阻塞队列中等待执行的任务会被作为返回值返回;
2.3 ThreadPoolExecutor (class)
- ThreadPoolExecutor是线程池核心类
2.3.1 构造方法源码
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;
}
- corePoolSize: 线程池的核心线程数目。当一个请求进来时,如果当前线程池中线程数量小于这个值时,则直接通过ThreadFactory新建一个线程来处理这个请求,如果已有线程数量大于等于这个值,则将请求放入阻塞队列中;
- maximumPoolSize : 线程池的最大线程数目。当线程池中线程数量已经等于corePoolSize,并且阻塞队列也已经满了,则看当前线程池中线程量是否小于该值,如果小于,则创建一个线程来处理请求,否则使用“饱和策略”来拒绝请求。对于大于corePoolSize部分的线程,称这部分线程为“idle threads”,这部分线程会有一个最大空闲时间,如果超过这个空闲时间还没有任务进来,则这些空闲线程会被回收。
- keepAliveTime 和 unit : 这两个参数用来控制 “idle threads” 的最大空闲时间。超过这个时间的 "idle thread"会被回收。
- 需要注意:ThreadPoolExecutor中有一个属性(allowCoreThreadTimeout),用来指定核心线程(在线程数量小于corePoolSize时创建的线程) 是否允许超时回收,默认为false,即核心线程不会超时回收,如果将改属性设置为true,则核心线程超过该空闲时间时也会被回收。
- workQueue: 阻塞队列。超过corePoolSize的线程请求会被方法阻塞队列。阻塞队列分为有界队列和无界队列,指定了“capacity”的队列是有界队列,否则是无界队列。
- 无界队列:使用无界队列作为阻塞队列后,线程池不会再创建非核心线程,同时也不会执行饱和拒绝策略,这时如果核心线程数远远不满足使用需求时,会导致大量的请求堆积,进而造成内存溢出(因为新的请求会先进入阻塞队列,当阻塞队列满后才会进行线程池扩充和饱和丢弃)。所以应当根据具体情况使用无界队列。
- ThreadFactory: 是一个线程工厂,用来为线程池创建线程。ThreadFactory接口下有许多实现类,包括 DefaultThreadFactory类。
- handler: 饱和策略,当线程池中总线程数量达到最大线程数时,用来拒绝多余的线程请求。RejectedExecutionHandler 接口有四个实现类,即有四种拒绝策略:
- DiscardPolicy:直接丢弃任务,不做其他任务处理;
- DiscardOldestPolicy:丢弃阻塞队列中最早的请求(即队列头的任务),将新的任务加入队尾;
- AbortPolicy:丢弃任务,并抛出 RejectedExecutionException 异常;
- CallerRunsPolicy:只要线程池未关闭,该策略直接在调用者线程中运行当前被丢弃的任务。这样做线程并不会真的被丢弃,但是很可能会导致提交任务的线程性能急剧下降;
2.3.2 线程创建流程说明
- 第一步:新请求来时,会先判断当前线程数是否小于核心线程数(corePoolSize),如果小于则直接创建;
- 第二步:如果核心线程数已满,则判断阻塞队列是否满,如果未满,则加入阻塞队列,等待调用;
- 第三步:如果阻塞队列已满,则判断当前线程数,是否小于最大线程数(maximumPoolSize),如果小于则进行线程扩充,创建非核心线程执行任务;
- 第四步:如果当前线程数已达到最大线程数,则执行饱和策略;
2.3.3 注意事项
- 线程池达到核心数量后,新请求会先进入阻塞队列,然后进行线程池扩容,最后进行饱和拒绝策略;
- 如果阻塞队列是无界的,则不会执行线程扩容和饱和拒绝;
- 无界队列有可能会导致内存溢出;
三、 Executors 工厂类
Executors是一个工厂类,主要用来创建ExecutorService
3.1 Executors 创建的几个常用的线程池
3.1.1 固定数量线程池 FixedThreadPool
创建方法源码:
// 1. 使用默认线程工厂对象创建新线程:DefaultThreadFactory
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads,