线程池的好处
Java中的线程池是运用场景最多的并发框架,几乎全部须要异步或并发执行任务的程序均可以使用线程池。在开发过程当中,合理地使用线程池,相对于单线程串行处理(Serial Processing)和为每个任务分配一个新线程(One Task One New Thread)的作法可以带来3个好处。数据库
- 下降资源消耗。经过重复利用已建立的线程下降线程建立和销毁形成的消耗。
- 提升响应速度。当任务到达时,任务能够不须要等到线程建立就能当即执行。
- 提升线程的可管理性。线程是稀缺资源,若是无限制地建立,不只会消耗系统资源,还会下降系统的稳定性,使用线程池能够进行统一分配、调优和监控。可是,要作到合理利用线程池,必须对其实现原理了如指掌。
线程池的实现原理
下面全部的介绍都是基于JDK 1.8源码。数组
架构设计
Java中的线程池核心实现类是ThreadPoolExecutor。这个类的设计是继承了AbstractExecutorService抽象类和实现了ExecutorService,Executor两个接口,关系大体以下图所示:安全

下面将从顶向下逐个介绍这个4个接口与类。服务器
Executor
顶层接口Executor提供了一种将任务提交和每一个任务的执行机制(包括线程使用的细节以及线程调度等)解耦分开的方法。使用Executor能够避免显式的建立线程。例如,对于一系列的任务,你可能会使用下列这种方式来代替new Thread(new(RunnableTask())).start()
的方式:多线程
Executor executor = anExecutor;
executor.execute(new RunnableTask1());
executor.execute(new RunnableTask2());
Executor接口提供了一个接口方法,用来在将来的某段时间执行指定的任务。指定的任务架构
- 可能由一个新建立的线程执行;
- 可能由一个线程池中空闲的线程执行;
- 也可能由方法的调用线程执行。
这些可能执行方式都取决于Executor接口实现类的设计或实现方式。并发
public interface Executor {
void execute(Runnable command);
}
Serial Processing
事实上,Executor接口并无严格的要求线程的执行须要异步进行。最简单的接口实现方法是,将全部的任务以调用方法的线程执行。框架
class DirectExecutor implements Executor {
public void execute(Runnable r) {
r.run();
}
}
这种实际上就是上面提到的Serial Processing的方式。假设,咱们如今以这种方式去实现一个响应请求的服务器应用。那么,这种实现方式虽然在理论上是正确的。异步
- 可是其性能却很是差,由于它每次只能响应处理一个请求。若是有大量请求则只能串行响应。
- 同时,若是服务器响应逻辑里面有文件I/O或者数据库操做,服务器须要等待这些操做完成才能继续执行。这个时候若是阻塞的时间过长,服务器资源利用率就很低。这样,在等待过程当中,服务器CPU将处于空闲状态。
综上,这种Serial Processing的方式方式就会有没法快速响应问题和低吞吐率问题。函数
One Task One New Thread
不过,更典型的实现方式是,任务由一些其余的线程执行而不是方法调用的线程执行。例如,下面的Executor的实现方法是对于每个任务都新建一个线程去执行。
class ThreadPerTaskExecutor implements Executor {
public void execute(Runnable r) {
new Thread(r).start();
}
}
这种方式实际上就是上面提到的One Task One New Thread的方式,这种无限建立线程的方法也有不少问题。
- 线程生命周期的开销很是高。若是有大量任务须要执行,那么就须要建立大量线程。这样就会形成线程生命周期的建立和销毁的开销很是大。
- 资源消耗。活跃的线程会消耗系统资源,尤为是内存。若是,已经有足够多的线程使全部的CPU保持忙碌状态,那么在建立更多的线程反而会下降性能。最简单的例子是,一个4核的CPU机器,对于100个任务建立100个线程去执行。
- 稳定性。可建立线程的数量上存在一个限制。这个限制受JVM启动参数,栈大小以及底层操做系统对线程的限制等因素。超过了这个限制,就可能抛出OutOfMemoryError异常。
ExecutorService
ExecutorService接口是继承自Executor接口,并增长了一些接口方法。接口也能够继承?之前没注意,如今学习到了。这里介绍下接口继承的语义:
- 接口Executor有execute(Runnable)方法,接口ExecutorService继承Executor,不用复写Executor的方法。只须要,写本身的方法(业务)便可。
- 当一个类ThreadPoolExecutor要实现ExecutorService接口的时候,须要实现ExecutorService和Executor两个接口的方法。
ExecutorService大体新增了2类接口方法:
- ExecutorService的关闭方法。对于线程池实现,这些方法的具体实如今ThreadPoolExecutor里面。
- 扩充异步执行任务的方法。对于线程池实现,用的这类方法都是AbstractExecutorService抽象类里面实现的模板方法。

AbstractExecutorService
抽象类AbstractExecutorService提供了ExecutorService接口类中各类submit异步执行方法的实现,这些方法与Executor.execute(Runnable)相比,它们都是有返回值的。同时,这些方法的实现的最终都是调用ThreadPoolExecutor类中实现的execute(Runnable)方法。
尽管说submit方法能提供线程执行的返回值,但只有实现了Callable才会有返回值,而实现Runnable的返回值是null。
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
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;
}
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
除此以外,这个抽象类中还有ExecutorService接口类中invokeAny和invokeAll方法的实现。这里就只是简单介绍下这2个种方法的语义。
invokeAny
- invokeAny() 接收一个包含 Callable 对象的集合做为参数。调用该方法不会返回 Future 对象,而是返回集合中某一个Callable对象的运行结果。
- 这个方法无法保证调用以后返回的结果是哪个Callable,只知道它是这些 Callable 中一个执行结束的Callable 对象。
invokeAll
- invokeAll接受一个包含 Callable 对象的集合做为参数。调用该方法会返回一个Future 对象的列表,对应输入的Callable 对象的集合的运行结果。
- 这里提交的任务容器列表和返回的Future列表存在顺序对应的关系。
ThreadPoolExecutor
execute(Runnable)方法
线程池是如何执行输入的任务,这个整个线程池实现的核心逻辑,咱们从这个方法开始学习。其代码以下所示:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
能够发现,当提交一个新任务到线程池时,线程池的处理流程以下:
- 判断线程池中工做的线程数是否小于核心线程数(corePoolSize)。若是是,则新建一个新的工做线程来执行任务(须要获取全局锁)。不然,进入下个流程。
- 判断线程池的工做队列(BlockingQeue)是否已满。若是未满,将新加的任务存储在工做队列中。不然,进入下个流程。
- 判断线程池中工做的线程数是否小于最大线程数(maximumPoolSize)。若是小于,则新建一个工做线程来执行任务(须要获取全局锁)。
- 若是大于或者等于,则交给饱和策略处理这个任务。
新提交任务处理流程图
以流程图来讲明的话,线程池处理一个新提交的任务的流程以下图所示:

ThreadPoolExecutor执行示意图

从上面的内容,咱们能够发现线程池对于一个新任务有4种处理的可能,分别对应于上面处理流程的4个步骤。
ThreadPoolExecutor采起上述步骤的整体设计思路,是为了在执行execute()方法时,尽量地避免获取全局锁(那将会是一个严重的可伸缩瓶颈)。在ThreadPoolExecutor完成预热以后(当前运行的线程数大于等于corePoolSize),几乎全部的execute()方法调用都是执行步骤2,而步骤2不须要获取全局锁。
工做线程
从上面execute(Runnable)的代码咱们能够发现,线程池建立线程时,会将线程封装成工做线程Worker,Worker在执行完任务后,还会循环获取工做队列里的任务来执行。
ThreadPoolExecutor中线程执行任务的示意图以下所示:

线程池中的线程执行任务分两种状况:
- 在execute()方法中建立一个线程时,会让这个线程执行当前任务。
- 这个线程执行完上图中1的任务后,会反复从BlockingQueue获取任务来执行。
ThreadPoolExecutor的ctl变量
ctl 是一个 AtomicInteger 的类,保存的 int 变量的更新都是原子操做,保证线程安全。它的前面3位用来表示线程池状态,后面29位用来表示工程线程数量。
ThreadPoolExecutor的状态
线程池的状态有5种:
- Running:线程池处在Running的状态时,可以接收新任务,以及对已添加的任务进行处理。线程池的初始化状态是RUNNING。换句话说,线程池被一旦被建立,就处于Running状态,而且线程池中的任务数为0。
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
- Shutdown: 线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加(正在运行的以及在BlockingQueue)的任务。调用线程池的shutdown()接口时,线程池由RUNNING -> SHUTDOWN。
- Stop: 线程池处在STOP状态时,不接收新任务,不处理已添加的任务,而且会中断正在运行的任务。 调用线程池的shutdownNow()接口时,线程池由(RUNNING or SHUTDOWN ) -> STOP。
- Tidying: 当全部的任务已终止,ctl记录的”任务数量”为0,线程池会变为Tidying状态。当线程池变为Tidying状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为Tidying时,进行相应的处理;能够经过重载terminated()函数来实现。
- Terminated: 线程池完全终止,就变成Terminated状态。 线程池处在Tidying状态时,执行完terminated()以后,就会由 Tidying -> Terminated。

线程池的使用
线程池的建立
咱们能够经过ThreadPoolExecutor的构造函数来建立一个线程池。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- corePoolSize(线程池的核心线程数):线程池要保持的线程数目,即便是他们是空闲也不会中止。 当提交一个任务到线程池时,线程池会建立一个线程来执行任务,即便其余空闲的基本线程可以执行新任务也会建立线程,等到须要执行的任务数大于线程池基本大小时就再也不建立。若是调用了线程池的prestartAllCoreThreads()方法,线程池会提早建立并启动全部基本线程。
- maximumPoolSize(线程池的最大线程数): 线程池容许建立的最大线程数。若是队列满了,而且已建立的线程数小于最大线程数,则线程池会再建立新的线程执行任务。值得注意的是,若是使用了无界的任务队列这个参数就没什么效果。
- keepAliveTime(线程活动保持时间): 当线程池中的线程数大于corePoolSize时,keepAliveTime为多余的空闲线程等待新任务的最长保持存活的时间。因此,若是任务不少,而且每一个任务执行的时间比较短,能够调大时间,提升线程的利用率。
- unit(线程活动保持时间的单位) : 可选的单位有天(DAYS)、小时(HOURS)、分钟(MINUTES)、毫秒(MILLISECONDS)、微秒(MICROSECONDS,千分之一毫秒)和纳秒(NANOSECONDS,千分之一微秒)。
- runnableTaskQueue(任务队列):用于保存等待执行的任务的阻塞队列。能够选择如下几个阻塞队列。
- ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按FIFO(先进先出)原则对元素进行排序。
- LinkedBlockingQueue:一个基于链表结构的无界阻塞队列,此队列按FIFO排序元素,吞吐量一般要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
- SynchronousQueue:一个不存储元素的阻塞队列。每一个插入操做必须等到另外一个线程调用移除操做,不然插入操做一直处于阻塞状态,吞吐量一般要高于Linked-BlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
- PriorityBlockingQueue:一个具备优先级的无限阻塞队列。
- ThreadFactory:用于设置建立线程的工厂,能够经过线程工厂给每一个建立出来的线程设置更有意义的名字。
-
RejectedExecutionHandler(饱和策略):当ThreadPoolExecutor已经关闭或ThreadPoolExecutor已经饱和 时(达到了最大线程池大小且工做队列已满),execute()方法将要调用的Handler,那么必须采起一种策略处理提交的新任务。这个策略默认状况下是AbortPolicy。Java线程池框架提供了如下4种策略:
- AbortPolicy:直接抛出异常
- CallerRunsPolicy:只用调用者所在线程来运行任务
- DiscardOldestPolicy:丢弃队列里最老的一个任务,并执行当前任务
- DiscardPolicy:不处理,丢弃掉
经常使用ThreadPoolExecutor
经过Executor框架的工具类Executors,能够建立如下3种类型的ThreadPoolExecutor。经过源码能够发现这3种线程池的本质都是不一样输入参数配置的ThreadPoolExecutor。
FixedThreadPool
FixedThreadPool被称为可重用固定线程数的线程池。下面是FixedThreadPool的源代码实现。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
注意到,
- FixedThreadPool的corePoolSize和maximumPoolSize都被设置为建立时的同一个指定的参数nThreads。
- 任务阻塞队列使用的是无界队列new LinkedBlockingQueue()。
- keepAliveTime设置为0。
- ThreadFactory和RejectedExecutionHandler皆使用的默认值。
FixedThreadPool的execute()方法的运行示意图以下所示:

其运行说明:
- 若是当前运行的线程数少于corePoolSize,则建立新线程来执行任务。
- 在线程池完成预热以后(当前运行的线程数等于corePoolSize),将任务加入LinkedBlockingQueue。
- 线程执行完1中的任务后,会在循环中反复从LinkedBlockingQueue获取任务来执行。
FixedThreadPool使用无界队列LinkedBlockingQueue做为线程池的工做队列(队列的容量为Integer.MAX_VALUE)对线程池会带来以下影响:
- 当线程池中的线程数达到corePoolSize后,新任务将在无界队列中等待。因为无界队列永远不会满,所以线程池中的线程数不会超过corePoolSize。
- 因为1,使用无界队列时maximumPoolSize将是一个无效参数。
- 因为1和2,使用无界队列时keepAliveTime将是一个无效参数。不会有超过corePoolSize的线程数目。
- 因为使用无界队列。运行中的FixedThreadPool(未执行方法shutdown()或shutdownNow())不会拒绝任务(不会调用RejectedExecutionHandler.rejectedExecution方法)。
SingleThreadExecutor
SingleThreadExecutor是使用单个worker线程的Executor。SingleThreadExecutor与FixedThreadPool相似,只是它的corePoolSize和maximumPoolSize被设置为1。下面是SingleThreadExecutor的源代码实现。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
CachedThreadPool
CachedThreadPool是一个会根据须要建立新线程的线程池。下面是建立CachedThread-Pool的源代码。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
注意到:
- CachedThreadPool的corePoolSize被设置为0,即corePool为空;maximumPoolSize被设置为 Integer.MAX_VALUE,即maximumPool是无界的。
- keepAliveTime设置为60L,意味着CachedThreadPool中的空闲线程等待新任务的最长时间为60秒,空闲线程超过60秒后将会被终止。
- CachedThreadPool使用没有容量的SynchronousQueue做为线程池的工做队列,但CachedThreadPool的maximumPool是无界的。这意味着,若是主线程提交任务的速度高于maximumPool中线程处理任务的速度时,CachedThreadPool会不断建立新线程。极端状况下,CachedThreadPool会由于建立过多线程而耗尽CPU和内存资源。
CacheThreadPool的execute()方法的执行过程以下图所示:

其执行过程的说明以下:
- 首先执行SynchronousQueue.offer(Runnable task)。若是当前maximumPool中有空闲线程正在执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS),那么主线程执行offer操做与空闲线程执行的poll操做配对成功,主线程把任务交给空闲线程执行;不然执行下面的步骤2。
- 当初始maximumPool为空,或者maximumPool中当前没有空闲线程时,将没有线程执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)。这种状况下,CachedThreadPool将会建立一个新线程执行任务。
- 步骤2中新建立的线程将任务执行完后,会执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)。这个poll操做会让空闲线程最多在SynchronousQueue中等待60秒钟。若是60秒钟内主线程提交了一个新任务(主线程执行步骤1),那么这个空闲线程将执行主线程提交的新任务;不然,这个空闲线程将终止。因为空闲60秒的空闲线程会被终止,所以长时间保持空闲的CachedThreadPool不会使用任何资源。
向线程池提交任务
可使用两个方法向线程池提交任务,分别为execute()和submit()方法。
- execute()方法用于提交不须要返回值的任务,因此没法判断任务是否被线程池执行成功。通常execute()方法输入的任务是一个Runnable类的实例。
- submit()方法用于提交须要返回值的任务。线程池会返回一个future类型的对象,经过这个future对象能够判断任务是否执行成功,而且能够经过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后当即返回,这时候有可能任务没有执行完。
关闭线程池
能够经过调用线程池的shutdown或者shutdownNow方法来关闭线程池。它们的原理是遍历线程池中的工做线程,而后逐个调用线程的interrupt方法来中断线程,因此没法响应中断的任务可能永远没法终止。可是它们存在必定的区别。
- shutdown首先将线程池的状态设置成SHUTDOWN。而后阻止新提交的任务,对于新提交的任务,若是测试到状态不为RUNNING,则抛出rejectedExecution 。对于已经提交(正在运行的以及在任务队列中的)任务不会产生任何影响。同时会将那些闲置的线程(idleWorkers)进行中断。
- shutdownNow首先将线程池的状态设置成STOP。而后阻止新提交的任务,对于新提交的任务,若是测试到状态不为RUNNING,则抛出rejectedExecution 同时会中断当前正在运行的线程。另外它还将BolckingQueue中的任务给移除,并将这些任务添加到列表中进行返回。
线程池的监控
能够经过线程池提供的参数进行监控,在监控线程池的时候可使用如下属性:
- taskCount:线程池须要执行的任务数量。
- completedTaskCount:线程池在运行过程当中已完成的任务数量,小于或等于taskCount。
- largestPoolSize:线程池里曾经建立过的最大线程数量。经过这个数据能够知道线程池是 否曾经满过。如该数值等于线程池的最大大小,则表示线程池曾经满过。
- getPoolSize:线程池的线程数量。若是线程池不销毁的话,线程池里的线程不会自动销 毁,因此这个大小只增不减。
- getActiveCount:获取活动的线程数。
另外,经过扩展线程池进行监控。能够经过继承线程池来自定义线程池,重写线程池的beforeExecute、afterExecute和terminated方法,也能够在任务执行前、执行后和线程池关闭前执行一些代码来进行监控。例如,监控任务的平均执行时间、最大执行时间和最小执行时间等。这几个方法在线程池里是空方法。