1000个并发线程,10台机器,每台机器

北京医院湿疹 http://m.39.net/pf/a_8839620.html

这是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容器里面的,对外提供


转载请注明:http://www.aierlanlan.com/rzdk/3961.html