Java线程池原理(一)
众所周知Executor框架中的ExecutorService
接口的实现就是ThreadPoolExecutor
线程池,本文《Java线程池原理》系列从源码角度捋一捋线程池的重点实现。
在此之前先聊聊ThreadPoolExecutor
的作者:Doug Lea
Doug Lea是 java.util.concurrent 包(JUC)的作者,不少网友评价Doug Lea是JDK史上最牛*的程序员,甚至有人感叹:“编程不识Doug Lea,写尽java也枉然”。
接下来盘点ThreadPoolExecutor
线程池的基本工作原理和Executors
工具类提供的几种常见线程池的效果,简单入个门。
线程池存在的意义
我们知道当下常见的jvm使用的线程都是内核级线程(KLT)。显然,线程是稀缺资源,它的创建与销般是一个相对偏重且耗资源的操作,而Java线程依赖于内核线程,创建线程需要进行操作系统状态切换,为避免资源过度消耗需要设法重用线程执行多个任务。
所以线程池就是一个线程缓存,负责对线程进行统一分配、调优与监控。
一般什么时候使用线程池?
- 单个任务处理时间比较短
- 需要处理的任务数量很大
- 比较耗时的任务不建议使用线程池,因为容易造成任务的堆积
线程池基本工作原理
ThreadPoolExecutor
jdk提供的线程池实现为ThreadPoolExecutor
类(常用就这个类)
其中前3个构造方法都依赖最后一个构造方法,其含义如下
1 | public ThreadPoolExecutor(int corePoolSize, /// 核心线程数 |
拓扑如下
线程工厂:负责创建线程
工作队列:一个保存任务的阻塞队列(阻塞队列:多线程下保证队列的FIFO特性)
1
2
3
4public interface ThreadFactory {
// 通过Runnable对象创建线程
Thread newThread(Runnable r);
}Worker:线程池实现线程复用以执行任务的核心对象(见下文),自身实现了
Runnable
接口且内部维护了一个Thread
线程对象核心线程:Worker对象内的线程,线程池常驻线程,成为核心线程的线程不会被销毁
非核心线程:Worker对象内的线程,成为非核心线程的线程进入空闲后,只会存活一定时间(keepAliveTime),之后便正常结束线程。
拒绝策略:任务无法添加时,线程池执行拒绝策略。
ThreadPoolExecutor
提供四种如图所示的拒绝策略,实现RejectedExecutionHandler
接口自身定义拒绝策略。1
2
3public interface RejectedExecutionHandler {
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
线程池的状态
类似线程的状态,线程池也有自己的状态
Running:运行状态,接受新任务,也能处理工作队列里的任务
Shutdown:关闭状态,不接受新任务,但是处理工作队列里的任务
Stop:停止状态,不接受新任务,不处理工作队列里的任务,中断正在处理中的任务
Tidying:整理状态,当所有的任务都执行完了,当前线程池已经没有工作线程,这时线程池将会转换为Tidying状态,并且将要调用terminated方法。terminated方法调用完毕之后,线程池进入Terminated状态。
1
protected void terminated() { } /// ThreadPoolExecutor terminated方法为空实现,子类可覆盖实现
Terminated:终止状态,terminated方法调用完毕之后,线程池进入Terminated状态。
其关系如图(对线程池工作原理有所了解后再回头看此状态转换图会更好)。
线程池添加任务流程
我相信大多数人都比较清楚线程池添加任务的流程,各种博客网图巴拉巴拉,这里再哔哔一下线程池提交任务的流程,如下:
我们通常通过执行ThreadPoolExecutor
实现Executor
接口的execute方法(Executor#execute(Runnable)
)来提交任务,这个流程就是来自该方法的实现!!!
贴一下源码,官方注释给你讲的明明白白,代码也很清晰。
1 | public void execute(Runnable command) { |
Executors工具类提供的常见线程池
可缓存线程池Executors.newCachedThreadPool()
1 | public static ExecutorService newCachedThreadPool() { |
分析:核心线程数为0,最大线程数为Integer.MAX_VALUE
,工作队列为同步移交队列;
新增任务:核心线程数为0 => 任务提交到工作队列 => 工作队列提交时阻塞直到有线程从队列获取任务。
很显然,每个任务都会交由非核心线程来完成。而非核心线程在任务全部执行完毕后只会存在一定时间(60s),所以这是具有缓存效果的线程池(Cached)。
固定线程数量线程池Executors.newFixedThreadPool()
1 | public static ExecutorService newFixedThreadPool(int nThreads) { |
分析:核心线程数为固定值,最大线程数与核心线程数相同,工作队列为无界队列;
无界队列:队列可存储的元素没有上限,概念上是如此,但实际受数据类型值范围限制,所以无界队列在Java中指可存放元素的最大值为Integer.MAX_VALUE
的队列。
LinkedBlockingQueue
无参构造方法
1 | public LinkedBlockingQueue() { |
新增任务:当核心线程数已满时,任务都存到工作队列,由于工作队列是无界的,所以队列永远不会满(概念上不会满),所以非核心线程没有存在的必要,即maximumPoolSize - corePoolSize = 0。
很显然,在固定线程数量线程池中,提交的每个任务都会交由核心线程来完成,核心线程数量固定,非核心线程数量为0,线程池常驻线程数量固定,所以称之为拥有固定线程数量的线程池(Fixed)。
单线程线程池Executors.newSingleThreadExecutor()
1 | public static ExecutorService newSingleThreadExecutor() { |
FinalizableDelegatedExecutorService
,仅重写了finalize方法,调用父类DelegatedExecutorService
线程池shutdown方法。
1 | static class FinalizableDelegatedExecutorService extends DelegatedExecutorService { |
DelegatedExecutorService
,该类实现的线程池内部维护了一个实际真正工作的线程池对象,实质是对线程池ExecutorService
的一个静态代理。
1 | static class DelegatedExecutorService extends AbstractExecutorService { |
分析:阅读源码后,不难发现该种线程池最终工作的线程池还是ThreadPoolExecutor
的实例,而ThreadPoolExecutor
实例的核心线程数为1,最大线程数也为1,工作队列为无界队列;
单线程线程池无非就是数量为1的固定数量线程池(Fixed,corePoolSize = maximumPoolSize = 1),与Fixed线程池不同的是,Single线程池对象在被GC回收时可自动关闭,比较安全,而Fixed线程池的关闭依赖于第三方调用者。
Single线程池更像一个“孤儿”,干活都是同一个Worker,自生自灭。
定时任务线程池
TODO
PS:在下博客里的图绝大部分都是自己通过ProcessOn做的,均带有**@WindShadow**水印,这么认真的开发人员哪里找哦
( ’ - ’ * )