线程池(英语:thread pool):一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络 sockets 等的数量。 例如,线程数一般取 cpu 数量 +2 比较合适,线程数过多会导致额外的线程切换开销。
Java 中的线程池是用 ThreadPoolExecutor 类来实现的. 本文就对该类的源码来分析一下这个类内部对于线程的创建, 管理以及后台任务的调度等方面的执行原理。
先看一下线程池的类图:
上图的目的主要是为了让大家知道线程池相关类之间的关系,至少赚个眼熟,以后看到不会有害怕的感觉。
Executor 框架接口
Executor 框架是一个根据一组执行策略调用,调度,执行和控制的异步任务的框架,目的是提供一种将”任务提交”与”任务如何运行”分离开来的机制。
下面是 ThreadPoolExeCutor 类图。Executors 其实是一个工具类,里面提供了好多静态方法,这些方法根据用户选择返回不同的线程实例。
从上图也可以看出来,ThreadPoolExeCutor 是线程池的核心。
J.U.C 中有三个 Executor 接口:
Executor:一个运行新任务的简单接口;
ExecutorService:扩展了 Executor 接口。添加了一些用来管理执行器生命周期和任务生命周期的方法;
ScheduledExecutorService:扩展了 ExecutorService。支持 Future 和定期执行任务。
其实通过这些接口就可以看到一些设计思想,每个接口的名字和其任务是完全匹配的。不会因为 Executor 中只有一个方法,就将其放到其他接口中。这也是很重要的单一原则。
ThreadPoolExeCutor 分析
在去具体分析 ThreadPoolExeCutor 运行逻辑前,先看下面的流程图:
该图是 ThreadPoolExeCutor 整个运行过程的一个概括,整个源码的核心逻辑总结起来就是:
创建线程:要知道如何去创建线程,控制线程数量,线程的存活与销毁;
添加任务:任务添加后如何处理,是立刻执行,还是先保存;
执行任务:如何获取任务,任务执行失败后如何处理?
下面将进入源码分析,来深入理解 ThreadPoolExeCutor 的设计思想。
构造函数
先来看构造函数:
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();
// 注意 workQueue, threadFactory, handler 是不可以为null 的,为空会直接抛出错误
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;
}