
大家好,我是一家不知名企业的知名工程师,除了负责后端开发任务,还要兼顾面试新人和培训新人的工作。在面试候选人的过程中,我比较倾向于问一些偏基础又较为开放的问题,用来看看候选人基础能力怎么样。比如今天要说的线程池。
把我的经验分先给大家,希望初学者既要注重基础,更要结合实践,多思考。
根据项目经验,我会先问候选人平时怎么使用线程池,得到的结果不出意外就是两种:
不管是哪一种回答,我还是希望能从候选人那里得到他们对线程池的理解,因此还是会问一下ThreadPoolExecutorService的几个参数相关的问题,除了基础非常差的候选人外,一般候选人都能回答上corePoolSize, maxPoolSize, BlockingQueue之间的关系:

老生常谈的问题,还是要走一下流程的,一般人面试前都会准备,也是为了让候选人放松心态进入状态,比如问一下:
问题1:线程的状态以及状态之间的流转?
这个地方我希望候选人说的尽量具体一点,不仅要明确说出有哪些状态,还要扩展到进入状态的方法和区别。

问题2:在上述流程基础上,我还会延伸的问一下线程的终止和唤起方法?
1. 终止:shutdown()和shutdownNow()都是用来用来关闭线程的方法,区别在于:
2. 唤起:notify()和notifyAll()都是用来用来唤醒调用wait()方法进入等待锁资源队列的线程,区别在于:
问题3:针对线程池满了以后,线程池的拒绝策略?
还是基础部分的考察,这样,整个现成的生命周期基本上算是考察完毕了。
回答出线程池这几个参数的作用以及他们之间的关系后,我一般会给一到两个线程池相关的题,看看候选人是否能思考出,比如:
问题4:工作中,在什么场景下使用过线程处理业务?
这样的问题就很开放了,主动权完全交给候选人,但是这很考察候选人在项目开发过程中,有没有勤于思考的习惯还有代码优化的意识。为什么这么说呢?
先讲一下线程的使用场景,我总结了2个:阻塞和依赖。
我最近还用到了一种异步的线程服务,就是使用 Guava Cache 的时候,启用子线程异步刷新 Cache 缓存,避免主线程查询时的阻塞。
针对候选人提供的业务场景,我们能做的事情也很多,可以把问题延伸到项目上,也可以继续聚焦线程本身再深挖一下实战应用,进一步考察候选人的线程设计能力:
问题5:假如提交到线程池中的任务,IO耗时占比是90%,计算耗时占比10%,忽略提交到线程池中的任务数量,在4C8G的机器上,理想情况下线程池中创建多少个线程是最优的?

一般平时只埋头写CURD的候选人,难以计算出来,当然也遇到了不少能算出来结果来的,线程数=(1/0.1) * 4 * 100% = 40。
强调一下:不管是那种公式算出来的结果,得出的都是理论数据,是参考值,具体线程数还要根据使用场景估算出用户量、并发量等数据,最后进行实际压测的结果为准。能说出这样的话会显得非常有严谨和经验。
然后,能算出来的候选人,我会问下其它问题,例如:
问题6:假如e有一类cpu密集性的任务,没有IO操作,日常的时候只有1个任务,流量高峰会有50个任务,4C8G的机器上,使用的线程池,如何设置corePoolSize, maxPoolSize以及BlockingQueue的大小?
这样的问题,我还没有遇到能回答的很完美的候选人,尤其是在面试紧张的情况下,一般得到类似于下的回答:
因此我会对候选人做一些引导,比如:回答core=4, max=50, queue=1w的,我会问他他设置的maxPoolSize有没有作用?明显队列设置成1w,这个队列太大,根本就不会满,maxPoolSize数量的线程永远不会被创建,说明候选人是随意设置的,没有经过思考,这个时间我会让他结合前面一个题再思考思考。
这一步后,有些等候人开始回答出用4个线程,队列50,但这样并不是最优的,因为日常每秒1个任务时,只需要一个线程就够了,创建出4个线程,就有3个浪费。
很显然:日常只需要一个线程,那么corePoolSize=1,而高峰时候,虽然任务有50个,但是只是4C的机器,对于cpu密集型任务,4个线程是最优的,因此理想情况下maxCorePoolSize=4,最后再看看队列,因为队列满了,max才会被创建,而我们需要让max快速被创建出来,又不会出现任务拒绝,因此,可将队列大小设置成46,那么线程池的行为如下:
问题7:......
差不多的了,多线程部分掌握到这里就可以了,留点时间问问其他的吧。