与有肝胆人共事,从无字句处读书。
——周恩来
线程池在日常面试中占比很大,主要是因为线程池内容涉及的知识点较广,比如涉及到队列、线程、锁等等,所以很多面试官喜欢把线程池作为问题的起点,然后延伸到其它内容,由于我们专栏已经说过队列、线程、锁面试题了,所以本章面试题还是以线程池为主。
答:答题思路从大到小,从全面到局部,总的可以这么说,线程池结合了锁、线程、队列等元素,在请求量较大的环境下,可以多线程的处理请求,充分的利用了系统的资源,提高了处理请求的速度,细节可以从以下几个方面阐述:
通过以上总分的描述,应该可以说清楚对线程池的理解了,如果是面对面面试的话,可以边说边画出线程池的整体架构图(见《ThreadPoolExecutor 源码解析》)。
答:以上 6 个类可以分成两大类:一种是定义任务类,一种是执行任务类。
日常的做法都是先根据定义任务类定义出任务来,然后丢给执行任务类去执行。
答:作用如下:
答:线程池构造器各个参数的含义如下:
当请求不断增加时,各个参数起的作用如下:
答:一般来说,coreSize 和 maxSize 在线程池初始化时就已经设定了,但我们也可以通过 setCorePoolSize、setMaximumPoolSize 方法动态的修改这两个值。
setCorePoolSize 的限制见如下源码:
// 如果新设置的值小于 coreSize,多余的线程在空闲时会被回收(不保证一定可以回收成功)
// 如果大于 coseSize,会新创建线程
public void setCorePoolSize(int corePoolSize) {
if (corePoolSize < 0)
throw new IllegalArgumentException();
int delta = corePoolSize - this.corePoolSize;
this.corePoolSize = corePoolSize;
// 活动的线程大于新设置的核心线程数
if (workerCountOf(ctl.get()) > corePoolSize)
// 尝试将可以获得锁的 worker 中断,只会循环一次
// 最后并不能保证活动的线程数一定小于核心线程数
interruptIdleWorkers();
// 设置的核心线程数大于原来的核心线程数
else if (delta > 0) {
// 并不清楚应该新增多少线程,取新增核心线程数和等待队列数据的最小值,够用就好
int k = Math.min(delta, workQueue.size());
// 新增线程直到k,如果期间等待队列空了也不会再新增
while (k-- > 0 && addWorker(null, true)) {
if (workQueue.isEmpty())
break;
}
}
}
setMaximumPoolSize 的限制见如下源码:
// 如果 maxSize 大于原来的值,直接设置。
// 如果 maxSize 小于原来的值,尝试干掉一些 worker
public void setMaximumPoolSize(int maximumPoolSize) {
if (maximumPoolSize <= 0 || maximumPoolSize < corePoolSize)
throw new IllegalArgumentException();
this.maximumPoolSize = maximumPoolSize;
if (workerCountOf(ctl.get()) > maximumPoolSize)
interruptIdleWorkers();
}
答:空闲线程回收的时机:如果线程超过 keepAliveTime 时间后,还从阻塞队列中拿不到任务(这种情况我们称为线程空闲),当前线程就会被回收,如果 allowCoreThreadTimeOut 设置成 true,core thread 也会被回收,直到还剩下一个线程为止,如果 allowCoreThreadTimeOut 设置成 false,只会回收非 core thread 的线程。
线程在任务执行完成之后,之所有没有消亡,是因为阻塞的从队列中拿任务,在 keepAliveTime 时间后都没有拿到任务的话,就会打断阻塞,线程直接返回,线程的生命周期就结束了,JVM 会回收掉该线程对象,所以我们说的线程回收源码体现就是让线程不在队列中阻塞,直接返回了,可以见 ThreadPoolExecutor 源码解析章节第三小节的源码解析。
答:可以的,ThreadPoolExecutor 提供了一些钩子函数,我们只需要继承 ThreadPoolExecutor 并实现这些钩子函数即可。在线程池任务执行之前实现 beforeExecute 方法,执行之后实现 afterExecute 方法。
答:可以自定义的,线程创建默认使用的是 DefaultThreadFactory,自定义话的只需要实现 ThreadFactory 接口即可;拒绝请求也是可以自定义的,实现 RejectedExecutionHandler 接口即可;在 ThreadPoolExecutor 初始化时,将两个自定义类作为构造器的入参传递给 ThreadPoolExecutor 即可。
答:详见《ThreadPoolExecutor 源码解析》中 1.4 小节。
答:详见《ThreadPoolExecutor 源码解析》中 2 小节。
答:线程执行任务完成之后,有两种结果:
答:这种是不对的,如果 keepAliveTime 设置成负数,在线程池初始化时,就会直接报 IllegalArgumentException 的异常,而设置成 0,队列如果是 LinkedBlockingQueue 的话,执行 workQueue.poll (keepAliveTime, TimeUnit.NANOSECONDS) 方法时,如果队列中没有任务,会直接返回 null,导致线程立马返回,不会无限阻塞。
如果想无限阻塞的话,可以把 keepAliveTime 设置的很大,把 TimeUnit 也设置的很大,接近于无限阻塞。
答:我们需要明确几点:
结合 1、2,当线程执行完成之后,自然就可以从 FutureTask 的 outcome 属性中拿到值。
如果我们弄清楚 ThreadPoolExecutor 的原理之后,线程池的面试题都很简单,所以建议大家多看看 《ThreadPoolExecutor 源码解析》这小节。