文章目录
一、 ThreadPoolExecutor 数据成员
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
ctl
主要用于存储线程池的工作状态以及池中正在运行的线程数。显然要在一个整型变量存储两个数据,只能将其一分为二。其中高3bit用于存储线程池的状态,低位的29bit用于存储正在运行的线程数。
1.1、线程池的状态
线程池具有以下五种状态,当创建一个线程池时初始化状态为 RUNNING 。
线程池 的状态 | 说明 |
---|---|
RUNNING |
允许提交并处理任务 |
SHUTDOWN |
不允许提交新的任务,但是会处理完已提交的任务 |
STOP | 不允许提交新的任务,也不会处理阻塞队列中未执行的任务,并设置正在执行的线程的中断标志位 |
TIDYING | 所有任务执行完毕,池中工作的线程数为0,等待执行terminated()勾子方法 |
TERMINATED |
terminated()勾子方法执行完毕 |
注意,这里说的是线程池的状态,而不是池中线程的状态。
调用线程池的 shutdown 方法,将线程池由 RUNNING(运行状态)转换为 SHUTDOWN状态。
调用线程池的 shutdownNow 方法,将线程池由 RUNNING 或 SHUTDOWN 状态转换为 STOP 状态。
SHUTDOWN 状态 和 STOP 状态 先会转变为 TIDYING 状态,最终都会变为 TERMINATED
// Packing and unpacking ctl
private static int runStateOf(int c) {
return c & ~CAPACITY; }
private static int workerCountOf(int c) {
return c & CAPACITY; }
private static int ctlOf(int rs, int wc) {
return rs | wc; }
ThreadPoolExecutor 同时提供上述三个方法用于池中的线程查看线程池的状态和计算正在运行的线程数。
1.2、ThreadPoolExecutor 的核心参数
private int largestPoolSize;
private final BlockingQueue<Runnable>workQueue;
private volatile long keepAliveTime;
private volatile int corePoolSize;
private volatile int maximumPoolSize;
private volatile ThreadFactory threadFactory;
private volatile RejectedExecutionHandler handler;
上述数据成员对线程池的性能也有很大的影响,我会将它们放到构造中讲解。
private final HashSet<Worker> workers= new HashSet<Worker>();
private long completedTaskCount;
private volatile boolean allowCoreThreadTimeOut;
private int largestPoolSize;
completedTaskCount
表示线程池已完成的任务数。
allowCoreThreadTimeeOut
表示是否允许核心线程在空闲状态下自行销毁。
largestPoolSize
表示线程池从创建到现在,池中线程的最大数量
1.3、workers
private final HashSet<Worker> workers = new HashSet<Worker>();
workers 是个HashSet容器,它存储的是Worker类的对象,Worker是线程池的内部类,它继承了Runnable接口,不严格的情况下,可以将一个Worker对象看成Thread对象,也就是工作的线程。shutdown和shutdownNow方法中会使用workers完成对所有线程的遍历。
1.4、mainLock
private final ReentrantLock mainLock =new ReentrantLock();
private final Condition termination = mainLock.newCondition();
mainLock 主要用于同步访问(或者说改变)线程池的状态以及线程池的各项参数,比如 completedTaskCount 和 workers 等。
在 awaitTermination
方法中,(mianLock的)termination是用于延时的条件队列。
二、 ThreadPoolExecutor 的构造函数
public ThreadPoolExecutor(intcorePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
线程池的构造函数参数多达7个,现在我们一一来分析它们对线程池的影响。
corePoolSize :
线程池中核心线程数的最大值maximumPoolSize :
线程池中能拥有最多线程数workQueue:
用于缓存任务的阻塞队列
2.1、corePoolSize、maximumPoolSize、workQueue 三者关系
我们现在通过向线程池添加新的任务来说明着三者之间的关系。
(1)如果没有空闲的线程执行该任务且当前运行的线程数少于 corePoolSize :
,则添加新的线程执行该任务。
(2)如果没有空闲的线程执行该任务且当前的线程数等于 corePoolSize :
,同时阻塞队列未满,则将任务入队列,而不添加新的线程。
(3)如果没有空闲的线程执行该任务且阻塞队列已满同时池中的线程数小于maximumPoolSize:
,则创建新的线程执行任务。
(4)如果没有空闲的线程执行该任务且阻塞队列已满同时池中的线程数等于maximumPoolSize:
,则根据构造函数中的 handler 指定的策略来拒绝新的任务。
注意,线程池并没有标记哪个线程是核心线程,哪个是非核心线程,线程池只关心核心线程的数量。
通俗解释, 如果把线程池比作一个单位的话, corePoolSize :
就表示正式工,线程就可以表示一个员工。当我们向单位委派一项工作时,如果单位发现正式工还没招满,单位就会招个正式工来完成这项工作。随着我们向这个单位委派的工作增多,即使正式工全部满了,工作还是干不完,那么单位只能按照我们新委派的工作按先后顺序将它们找个地方搁置起来,这个地方就是 workQueue:
,等正式工完成了手上的工作,就到这里来取新的任务。如果不巧,年末了,各个部门都向这个单位委派任务,导致 workQueue:
已经没有空位置放新的任务,于是单位决定招点临时工吧(临时工:又是我!)。临时工也不是想招多少就找多少,上级部门通过这个单位的 maximumPoolSize:
确定了你这个单位的人数的最大值,换句话说最多招maximumPoolSize – corePoolSize:
个临时工。当然,在线程池中,谁是正式工,谁是临时工是没有区别,完全同工同酬。
参考文章:
corePoolSize、maximumPoolSize、workQueue 三者关系的文章:
https://www.cnblogs.com/cdf-opensource-007/p/8769777.html
2.2、keepAliveTime、TimeUnit 存活时间和单位
参考keepAliveTime :
表示空闲线程的存活时间。
参考TimeUnit unit :
表示keepAliveTime的单位。
为了解释 keepAliveTime
的作用,我们在上述情况下做一种假设。假设线程池这个单位已经招了些临时工,但新任务没有继续增加,所以随着每个员工忙完手头的工作,都来 workQueue
领取新的任务(看看这个单位的员工多自觉啊)。随着各个员工齐心协力,任务越来越少,员工数没变,那么就必定有闲着没事干的员工。这样的话领导不乐意啦,但是又不能轻易fire没事干的员工,因为随时可能有新任务来,于是领导想了个办法,设定了 keepAliveTime
,当空闲的员工在 keepAliveTime
这段时间还没有找到事情干,就被辞退啦,毕竟地主家也没有余粮啊!当然辞退到 corePoolSize
个员工时就不再辞退了,领导也不想当光杆司令啊!
2.3、workQueue 任务队列
workQueue
它决定了缓存任务的排队策略。对于不同的应用场景我们可能会采取不同的排队策略,这就需要不同类型的队列。这个队列需要一个实现了BlockingQueue接口的任务等待队列。
在ThreadPoolExecutor线程池的API文档中,一共推荐了三种等待队列,它们是: SynchronousQueue
、LinkedBlockingQueue
和 ArrayBlockingQueue
可参考:https://blog.csdn.net/xiaojin21cen/article/details/87363143
2.3.2、有限队列
- SynchronousQueue :(一个不存储元素的阻塞队列) 一个 不存储元素的阻塞队列
。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于
阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool
使用了这个队列。 - ArrayBlockingQueue:(有界阻塞队列) 一个由数组支持的有界阻塞队列。此队列按 FIFO(先进先出)原则对元素进行排序。
新元素插入到队列的尾部,队列获取操作则是从队列头部开始获得元素。这是一个典型的“有界缓存区”,固定大小的数组在其中保持生产者插入的元素和使用者提取的元素。一旦创建了这样的缓存区,就不能再增加其容量。试图向已满队列中放入元素会导致操作受阻塞;试图从空队列中提取元素将导致类似阻塞。
2.3.3、 无限队列
- LinkedBlockingQueue:(链表结构的阻塞队列,尾部插入元素,头部取出元素)
LinkedBlockingQueue
是我们在ThreadPoolExecutor线程池中常用的等待队列。它可以指定容量也可以不指定容量。由于它具有“无限容量”的特性,所以我还是将它归入了无限队列的范畴(实际上任何无限容量的队列/栈都是有容量的,这个容量