这是why哥的第71篇原创文章
一道面试题
兄弟们,怎么说?
我觉得如果你工作了两年左右的时间,或者是突击准备了面试,这题回答个八成上来,应该是手到擒来的事情。这题中规中矩,考点清晰,可以说的东西不是很多。
但是这都上血书了,那不得分析一波?
先把这个面试题拿出来一下:
多个并发线程,10台机器,每台机器4核,设计线程池大小。
这题给的信息非常的简陋,但是简陋的好处就是想象空间足够大。
第一眼看到这题的时候,我直观的感受到了两个考点:
线程池设计。负载均衡策略。我就开门见山的给你说了,这两个考点,刚好都在我之前的文章的射程范围之内:
《如何设置线程池参数?美团给出了一个让面试官虎躯一震的回答》
《吐血输出:2万字长文带你细细盘点五种负载均衡策略》
下面我会针对我感受到的这两个考点去进行分析。
线程池设计
我们先想简单一点:个并发线程交给10台机器去处理,那么1台机器就是承担个并发请求。
个并发请求而已,确实不多。
而且他也没有说是每1秒都有个并发线程过来,还是偶尔会有一次个并发线程过来。
先从线程池设计的角度去回答这个题。
要回答好这个题目,你必须有两个最基本的知识贮备:
自定义线程池的7个参数。JDK线程池的执行流程。先说第一个,自定义线程池的7个参数。
java.util.concurrent.ThreadPoolExecutor#ThreadPoolExecutor
害,这7个参数我真的都不想说了,你去翻翻历史文章,我都写过多少次了。你要是再说不出个头头是道的,你都对不起我写的这些文章。
而且这个类上的javadoc已经写的非常的明白了。这个javadoc是DougLea老爷子亲自写的,你都不拜读拜读?
为了防止你偷懒,我把老爷子写的粘下来,我们一句句的看。
关于这几个参数,我通过这篇文章再说最后一次。
如果以后的文章我要是再讲这几个参数,我就不叫why哥,以后你们就叫我小王吧。
写着写着,怎么还有一种生气的感觉呢。似乎突然明白了当年在讲台上越讲越生气的数学老师说的:这题我都讲了多少遍了!还有人错?
好了,不生气了,说参数:
corePoolSize:thenumberofthreadstokeepinthepool,eveniftheyareidle,unless{
codeallowCoreThreadTimeOut}isset(核心线程数大小:不管它们创建以后是不是空闲的。线程池需要保持corePoolSize数量的线程,除非设置了allowCoreThreadTimeOut。)maximumPoolSize:themaximumnumberofthreadstoallowinthepool。(最大线程数:线程池中最多允许创建maximumPoolSize个线程。)keepAliveTime:whenthenumberofthreadsisgreaterthanthecore,thisisthemaximumtimethatexcessidlethreadswillwaitfornewtasksbeforeterminating。(存活时间:如果经过keepAliveTime时间后,超过核心线程数的线程还没有接受到新的任务,那就回收。)unit:thetimeunitforthe{codekeepAliveTime}argument(keepAliveTime的时间单位。)workQueue:thequeuetouseforholdingtasksbeforetheyareexecuted.Thisqueuewillholdonlythe{codeRunnable}taskssubmittedbythe{codeexecute}method。(存放待执行任务的队列:当提交的任务数超过核心线程数大小后,再提交的任务就存放在这里。它仅仅用来存放被execute方法提交的Runnable任务。所以这里就不要翻译为工作队列了,好吗?不要自己给自己挖坑。)threadFactory:thefactorytousewhentheexecutorcreatesanewthread。(线程工程:用来创建线程工厂。比如这里面可以自定义线程名称,当进行虚拟机栈分析时,看着名字就知道这个线程是哪里来的,不会懵逼。)handler:thehandlertousewhenexecutionisblockedbecausethethreadboundsandqueuecapacitiesarereached。(拒绝策略:当队列里面放满了任务、最大线程数的线程都在工作时,这时继续提交的任务线程池就处理不了,应该执行怎么样的拒绝策略。)第一个知识贮备就讲完了,你先别开始背,这玩意你背下来有啥用,你得结合着执行流程去理解。接下来我们看第二个:JDK线程池的执行流程。
一图胜千言:
关于JDK线程池的7个参数和执行流程。
虽然我很久没有参加面试了,但是我觉得这题属于必考题吧。
所以如果你真的还不会,麻烦你写个Demo,换几个参数调试一下。把它给掌握了。
而且还得多注意由这些知识点引申出来的面试题。
比如从图片也可以看出来,JDK线程池中如果核心线程数已经满了的话,那么后面再来的请求都是放到阻塞队列里面去,阻塞队列再满了,才会启用最大线程数。
但是你得知道,假如我们是web服务,请求是通过Tomcat进来的话,那么Tomcat线程池的执行流程可不是这样的。
Tomcat里面的线程池的运行过程是:如果核心线程数用完了,接着用最大线程数,最后才提交任务到队列里面去的。这样是为了保证响应时间优先。
所以,Tomcat的执行流程是这样的:
其技术细节就是自己重写了队列的offer方法。在这篇文章里面说的很清楚了,大家可以看看:
《每天都在用,但你知道Tomcat的线程池有多努力吗?》
好的,前面两个知识点铺垫完成了。
这个题,从线程池设计的角度,我会这样去回答:
前面我们说了,10个机器,个请求并发,平均每个服务承担个请求。服务器是4核的配置。
那么如果是CPU密集型的任务,我们应该尽量的减少上下文切换,所以核心线程数可以设置为5,队列的长度可以设置为,最大线程数保持和核心线程数一致。
如果是IO密集型的任务,我们可以适当的多分配一点核心线程数,更好的利用CPU,所以核心线程数可以设置为8,队列长度还是,最大线程池设置为10。
当然,上面都是理论上的值。
我们也可以从核心线程数等于5开始进行系统压测,通过压测结果的对比,从而确定最合适的设置。
同时,我觉得线程池的参数应该是随着系统流量的变化而变化的。
所以,对于核心服务中的线程池,我们应该是通过线程池监控,做到提前预警。同时可以通过手段对线程池响应参数,比如核心线程数、队列长度进行动态修改。
上面的回答总结起来就是四点:
CPU密集型的情况。IO密集型的情况。通过压测得到合理的参数配置。线程池动态调整。前两个是教科书上的回答,记下来就行,面试官想听到这两个答案。
后两个是更具有实际意义的回答,让面试官眼前一亮。
基于这道面试题有限的信息,设计出来的线程池队列长度其实只要大于就可以。
甚至还可以设置的极限一点,比如核心线程数和最大线程数都是4,队列长度为96,刚好可以承担这个请求,多一个都不行了。
所以这题我觉得从这个角度来说,并不是要让你给出一个完美的解决方案,而是考察你对于线程池参数的理解和技术的运用。
面试的时候我觉得这个题答到这里就差不多了。
接下来,我们再发散一下。
比如面试官问:如果我们的系统里面没有运用线程池,那么会是怎么样的呢?
首先假设我们开发的系统是一个运行在Tomcat容器里面的,对外提供