承上启下:上一篇文章小豹子讲了我为何想要研究线程池的代码,以及我计划要怎样阅读代码。这篇文章我主要阅读了线程池实例化相关的代码,并提出了本身的疑问。java
咱们首先看构造器的声明,ThreadPoolExecutor
有四个重载构造器,其中三个分别指定了不一样的缺省参数值,咱们直接看参数最全的构造器:数据库
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) 复制代码
参数有点多,咱们有点懵,但并非无从下手。咱们去看代码上方的 JavaDoc:服务器
allowCoreThreadTimeOut
corePoolSize
)时,大于核心池数量部分的线程空闲持续 keepAliveTime
时间后,将被终止keepAliveTime
参数的时间单位execute
方法提交的 Runnable
任务executor
建立新线程时使用的线程工厂那么咱们根据文档来建立一个线程池:并发
@Test
public void newInstanceTest() {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(),
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread();
}
}, new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("拒绝服务");
}
});
}
复制代码
这里咱们建立了一个核心池数量为 5,最大线程数为 10,线程保持时间为 60 秒的线程池。ide
咱们跟踪到代码中,看实例化的过程当中,构造器为咱们作了什么:函数
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;
}
复制代码
这里很容易理解,前面进行了输入参数的检查,this.acc
是访问控制器上下文,这里咱们不深刻研究它。惟一值得一提的就是 unit.toNanos(keepAliveTime)
,这是将参数中的 keepAliveTime
转换成纳秒,彷佛也不难理解,但我有一个疑问:为何要抽象时间单位?抽象时间段很差么?好比我设计一个 Period
类表示一段时间,里面有几个静态方法用于实例化,好比 Period.fromSeconds(long n)
表示 n
秒的一段时间,而后可使用 Period#toNanos()
这类的方法将该段时间传化为纳秒。这样能够是参数更简洁,表意更明确。不知两种设计方案的优缺,还望各位指点。高并发
咱们继续看 ThreadPoolExecutor
的初始化:post
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
复制代码
又是一堆天书,但彷佛 RUNNING
、SHUTDOWN
等是表示某种状态的常量,至于它们的赋值为何这么特殊,其余变(常)量都是干吗的?老套路,看文档。性能
文档告诉咱们:ctl
是表示线程池状态的原子整形,它包含两部分:工做线程数、运行状态。为了将两个变量用一个原子整形表示,咱们限制工做线程数最多只能有 (2^29)-1
(大概 5 亿)个,而空余的高三位用来存储运行状态。学习
运行状态可能有这些值:
TIDYING
时将调用 terminated()
回调方法terminated()
方法完成后这些值之间的顺序很重要,运行状态的值随时间单调递增,但在一个生命周期内不须要经历过全部的状态。
状态的转换:
shutdown()
触发,或者隐含在 finalize()
中shutdownNow()
触发terminated()
执行结束以后看过文档以后,咱们再回头看这几个常量的赋值:首先 COUNT_BITS
是 Integer
的长度减 3,其余几个状态量分别是 -一、0、1,2,3 向高位移动 COUNT_BITS
位的结果,这也就对应着文档所写,用一个整形的高三位来存储线程池的状态。CAPACITY
的值是 1 向高位移动 COUNT_BITS
位再减一,字面意思是容量,这不难理解,COUNT_BITS
就是表明线程池所能容纳的最大线程数,而值得一提的是,这个值在二进制层面上具备另外一个意义:CAPACITY
的二进制值高三位为 0,其余位为 1。具体用途,咱们后面细说。
如今只剩 ctl
咱们不清楚了,首先从文档中咱们能够获知 ctl
是包含了运行状态与线程数量的一个整形原子变量,那么 ctlOf(RUNNING, 0)
是什么意思呢?咱们来看 ThreadPoolExecutor
中的静态方法:
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; }
private static boolean runStateLessThan(int c, int s) {
return c < s;
}
private static boolean runStateAtLeast(int c, int s) {
return c >= s;
}
private static boolean isRunning(int c) {
return c < SHUTDOWN;
}
复制代码
这里小豹子带你们回忆一下位运算:
&
是按位与运算符,输入均为 1 输出为 1,其余为 0;
|
是按位或运算符,输入均为 0 输出为 0,其余为 1;
~
是按位非运算符,输入为 0 输出为 1,输入为 1 输出为 0;
咱们看 ctlOf(int rs, int wc)
,其中 rs 指运行状态(runState),wc 值线程数(workerCount)。rs 值的特色是高三位表示运行状态,而其余低位均为 0,wc 值的特色是高三位为 0(由于不大于 CAPACITY
嘛),低位表示线程数。那么对两个值进行按位或运算,正好就将两个值的有效位合并到一个整形变量中。咱们再回头看 ctl
变量的初始化 new AtomicInteger(ctlOf(RUNNING, 0))
。这回应该就清楚了,ctlOf(RUNNING, 0)
表示运行状态是 RUNNING
,线程数为 0 的线程池状态。
那么 runStateOf
与 workerCountOf
就没必要多说,是从 ctl
中剥离出运行状态值和线程数,在这里 CAPACITY
的做用就体现出来,它表示一种标志位,由于它二进制值的特性(前文提到)使得另外一个值与它进行位与(或非与)运算时能够获得值的低位(或高位)。接下来我着重解释一下 isRunning(int c)
,首先咱们要已知两个事实:
RUNNING
是最小的,SHUTDOWN
次之。ctl
变量的高三位。那么判断当前线程池的状态是否为 RUNNING
,有没有必要将 ctl
中的状态值提取出来,再与 RUNNING
常量进行对比呢?没有必要,由于状态值占高位,只要状态值小于 SHUTDOWN
,ctl
就必然小于 SHUTDOWN
,而小于 SHUTDOWN
的状态只有 RUNNING
,所以只要 ctl
值小于 SHUTDOWN
,它就必定是 RUNNING
状态。其余函数(runStateLessThan
、runStateAtLeast
)同理,直接对比就好。
看到 ThreadPoolExecutor
中用一个原子变量存储两种状态的设计思想,我心中产生一个疑问:为何要这样作?为了节省内存么?确定不是,线程池的主要应用场景应该是服务器,而用时间换空间(还只换了这么点空间)是很是不值得的。那么我惟一能想到的解释是,有利于提升并发性能。
我记得我在看《高性能 MySQL》的时候,做者告诉我这样一种思想:热点分离。
书中描绘了这样一个应用场景,一个相似微博的应用,后台要统计总发贴数。那么每一次获取数据都要 count(*)
这确定不现实。现实一点的作法是,在数据库中维护一个表示总发贴数的记录,每一次用户发帖,这个值就加 1。这种方案并发性能也不是很好。由于这个字段至少要加行锁,每次用户发帖,总发贴数加 1 时都会引发锁竞争。这至关于把用户发帖行为串行化了。
书中的解决方案是设计一张表,其中有 n 条记录(好比说 100 条),每一次用户发帖,在这 100 条记录中选一条记录(能够是随机选择,也能够根据时间取模)自加 1。而后每隔一段时间将表中的全部记录加和赋值到第一条记录中,删除其余记录。这样一来,原先是 N 个线程争抢一把锁,如今是 N 个线程争抢一百把锁。并发性能固然获得了增长。这就是所谓的热点分离。
但 ThreadPoolExecutor
中 ctl
的设计彷佛反其道而行之。把两个须要并发访问的值“捏”到了一块儿。除非运行状态和线程数每每同时变化,不然这样作,我理解不了它是怎样提升并发性能的。我决定暂时搁置这个问题,在后续对源码的学习过程当中,我相信我能获得答案。
小豹子仍是一个大三的学生,小豹子但愿你能“批判性的”阅读本文,对本文内容中不正确、不稳当之处进行严厉的批评,小豹子感激涕零。