当前位置: 首页 > 科技观察

面试被问到线程池,太香了

时间:2023-03-20 01:54:23 科技观察

本文转载自微信公众号《Angela的博客》,作者Angela的博客。转载本文请联系Angela博客公众号。这是并发编程系列的第五篇文章。说到并发编程,为什么线程池这么少?在阿里有很多使用线程池的场景。善用线程池是日常开发中必须掌握的利器。下面说说2019年的晚上,在线程池上和一个面试官激战了半个小时。面试官:简历上写了对系统性能进行了优化,可以简单介绍一下吗?有哪些优化,如何衡量优化效果?我:巴拉巴拉。..例如,我们的系统用于查询用户的个人身份信息、联系方式、订单状态信息、积分信息等。以前的系统是用单线程串行处理的。我使用线程池并行处理四个任务,然后合并处理结果。面试官:你刚才说你用的是线程池。你能告诉我你为什么使用线程池吗?我可以创建四个线程进行处理吗?我:是的,当然。我:但是用线程池更合适。阿里巴巴的开发规范中有一条:3.【强制】线程资源必须通过线程池提供,不允许在应用中显式创建线程。说明:使用线程池的好处是减少创建和销毁线程所花费的时间和系统资源的开销,解决资源不足的问题。如果不使用线程池,可能会导致系统创建大量相同类型的线程,导致内存消耗或“过度切换”。?我:就像你去饭店吃饭,服务员总是提前洗碗,不会等到你来做饭。菜品就像是线程池中的线程,你要处理cookingTask。面试官:你知道线程池的相关类关系吗?我:这是什么问题?不应该是问我怎么设置核心线程数吗?好的。..请看下图:Executor的定义很简单,它定义了线程池中最本质的事情,执行任务。publicinterfaceExecutor{voidexecute(Runnablecommand);}ExecutorService也是一个接口,但是它可以看作是搭建了线程池的框架,告诉线程池实现它必须提供一些管理线程池的方法。AbstractExecutorService是一个普通的线程池执行器,ScheduledExecutorService是一个定时任务线程池。面试官:那你在日常开发中是怎么创建线程池的呢?我:我使用ThreadPoolExecutor创建自定义线程池。面试官:你知道线程池的核心参数吗?我:线程池主要有7个核心参数。让我们看看ThreadPoolExecutor构造函数。corePoolSize:核心线程数maximumPoolSize:最大线程数池中未被销毁的空闲时间。如果线程池中线程过多,任务比较少,此时线程池就会被销毁。unit:keepAliveTime的时间单位,一般设置为秒或毫秒。workQueue:任务队列,存放等待执行的任务threadFactory:创建线程的任务工厂,比如给线程名加前缀,后面会讲到handler:拒绝任务处理器,当任务无法处理时,拒绝processorallowCoreThreadTimeOut:是否允许核心线程超时销毁。这个参数不在构造函数中,但也很重要。采访者:说实话,你来之前有没有背过,不然你怎么能全记住。我:[转桌子]不想见面,你找什么工作,要什么自行车。来这里之前,我刚刚阅读了“Angela'sBlog”上的所有文章公众号。采访者:其实刚才也问过这个问题。让我们继续检查面试官是否诚实。.面试官:刚才提到了这些核心参数,能不能介绍一下线程池的基本工作原理。我:对,这里我给你画一个流程,如下:面试官:然后按照上面的流程写一个伪代码。我:我们能不能玩得开心,让线程池手撕。那你看流程图,代码如下:面试官:对,你平时是怎么管理线程池的?我:我会创建一个线程池管理器,比如ThreadPoolManager,里面有一个私有变量Map,根据线程池的功能给他起个名字,比如命名为:preparePlateThreadPool(准备板线程池),定义线程池名称作为常量,并将创建的线程池放入管理器的映射中。面试官:除了自己用ThreadPoolExecutor创建线程池,还有别的办法吗?我:java.util.concurrent包中提供的Executors也可以用来创建线程池。面试官:定义了哪些类型的Executor?我:newSingleThreadExecutos单线程线程池,也就是线程池只有一个任务,我偶尔会用newFixedThreadPool(intnThreads)固定大小线程的线程池newCachedThreadPool()无界线程池,这意味着无论有多少任务有了,线程被创建来运行它们,所以队列就相当于没用了。面试官:你说你日常开发用ThreadPoolExecutor创建线程池,为什么不用Executors。我:首先是Executors提供的线程池在使用场景上非常有限,一般场景很难使用。其次,都是通过ThreadPoolExecutor创建的线程池。我直接使用ThreadPoolExecutor创建线程池。原理通俗易懂,变通更灵活。高的。参考阿里开发手册规定:4.【强制】不允许使用Executors创建线程池,但使用ThreadPoolExecutor。这种做法可以让同学们更加清楚线程池的运行规律,避免资源耗尽的风险。注意:Executors返回的线程池对象的缺点如下:1)FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能累积大量请求,导致OOM。2)CachedThreadPool:??允许创建的线程数为Integer.MAX_VALUE,可能会创建大量线程,导致OOM。《阿里巴巴研发手册》面试官:前面你的代码中有一个任务入队的操作。您通常自定义线程池。你用什么队列?我:这个要看实际应用。有些任务是早上8点和下午6点的高峰时间,因此存在任务尖峰。使用了LinkedBlockingQueue,它是一个无界队列,不限制任务的大小。对于用于不是那么重要和非强依赖的任务的ArrayBlockingQueue,这是一个指定的大小。如果任务超出,则会创建一个非核心线程来执行任务。面试官:那怎么保证任务队列的可用性呢?我:有几个方面:我的线程池管理器会有一个定时任务定时检测Map中线程池中当前任务队列的状态,并且会设置一个waterThreshold(水位),如果有就会报警超过水位;每天的大促活动都会对线程池进行压力测试。限流、降级等保障。面试官:那线程池,核心任务个数,任务队列大小如何合理拆分?我:这是一个老生常谈的问题。【建议】要了解每个服务大概的平均耗时,可以配置独立线程池,将速度较慢的服务与主线程池隔离,这样所有服务线程就不会同时死掉。《阿里巴巴研发手册》根据任务类型,将任务拆分到不同的线程池中,并分别命名;区分任务类型,是CPU密集型还是IO密集型,CPU可以设置为CPU核数左右,上下文切换较少。io-intensive类型可以设置大一些。粗略估计一个,然后做压力测试和评估。另外,线程池中还有一个变量也可以参考:largestPoolSize,线程池达到的最大线程任务。该参数达到的最大值,同时参考系统的性能指标,如cou、io、mem等。这里还有一个公式供参考:最佳线程数=((线程等待时间+threadCPUtime)/threadCPUtime)*CPUnumber还有一个开源的辅助方法来计算线程池中合理的线程数。面试官:拒绝策略呢?你明白吗?我:拒绝策略是指当任务过多,超过maximumPoolSize时,只能拒绝。面试官:说详细一点:拒绝的时候可以指定拒绝策略,也可以自己实现。JDK默认提供了四种拒绝策略。AbortPolicy默认拒绝策略,直接抛出RejectedExecutionExceptionDiscardPolicy任务直接丢弃,调用CallerRunsPolicy或者执行被拒绝的任务,比如主线程调用线程池的submit提交任务,但是任务被拒绝,主线程直接执行。但是如果线程池已经关闭,则任务被丢弃。publicvoidrejectedExecution(Runnabler,ThreadPoolExecutore){//线程池不关闭if(!e.isShutdown()){//直接运行,不让线程池执行r.run();}}DiscardOldestPolicy丢弃最长在任务队列中等待,然后尝试执行被拒绝的任务。但是如果线程池已经关闭,任务就会被丢弃poll();//线程池尝试执行任务e.execute(r);}}面试官:拒绝的类型有哪些面试官:你选择了哪些拒绝策略?我:我选择拒绝回答面试官:我选择你回去等通知。年底了,蚂蚁现在在放仓。有挑战性的业务场景,6位数的QPS,进程加速。年底了,过完年就可以直接来上班了。在这一点上几乎没有竞争压力。不管你来不来,都可以来找我聊天,我的微信:guofu-angela。