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

面试官:你如何评价一个线程池需要设置多少个线程

时间:2023-03-22 14:14:02 科技观察

?看单词的发音。我是伟哥,一名普通中学毕业生。技术分享实现职场转型,成长为RocketMQ社区优秀布道者,大厂高级架构师,出书《RocketMQ技术内幕》。欢迎大家关注我,一起交流,共同进步。Java并发编程是大厂第一轮面试的高频面试题,线程池就是其中的典型代表。本文将对线程池的工作机制进行梳理,并提出灵魂之问:您如何看待线程池的工作机制?有了这样的认识,在工作中如何判断一个线程池需要创建多少个线程呢?一、线程池基本工作原理及面试指南1.1java线程池核心属性JAVA线程池核心属性如下:intcorePoolSizecore线程数intmaximumPoolSize线程池最大线程数longkeepAliveTime线程保持时间activeTimeUnitunitkeepAliveTime时间单元BlockingQueueworkQueue任务挤压队列ThreadFactorythreadFactory线程创建工厂类RejectedExecutionHandlerhandler拒绝策略1.2向线程池提交任务时的线程创建过程当用户向线程池提交任务时,线程池将如何处理创建一个线程?首先,线程池会判断当前创建的线程是否小于corePoolSize(核心线程数)。如果比较小,那么不管创建的线程是否空闲,都会选择创建一个新的线程来执行任务,直到创建的线程数等于核心线程数。当线程池中创建的线程数等于core核心线程数时,当用户继续向线程池提交任务时,会先判断任务队列是否满:1)如果任务队列满没满,把任务放到队列中间。2)如果任务队列已满,则判断当前线程数是否超过最大线程数,如果没有,则创建新线程执行任务,如果线程池已创建最大线程数等线程数,然后执行拒绝策略。提醒:所以如果线程池使用的队列是无界队列,最大线程数就会变得没有意义。1.3线程池拒绝策略及使用场景JUC默认提供以下拒绝策略:AbortPolicy拒绝,直接抛出RejectedExecutionException,默认值。CallerRunsPolicy直接由调用线程运行任务的run方法,即异步转同步。DiscardOldestPolicy丢弃任务队列中最早的任务。如果DiscardPolicy拒绝,则不会执行,就好像没事做一样。拒绝策略触发的条件:线程池使用有界任务队列才能触发。当队列已满且线程池创建的线程已达到线程池允许的最大数量时。默认情况下,AbortPolicy通常就足够了。CallerRunsPolicy异步转同步其实在rejection的情况下意义不大,我也没有想到合适的场景,因为在需要执行rejectionpolicy的时候,处理已经变慢了,同步执行任务只会增加在服务器上加载。对恢复问题很有用。DiscardOldestPolicy该策略通常用于录制曲目。偶尔丢失一些数据不要紧,但希望能保存最新的数据。DiscardPolicy策略通常用于异步打印日志,直接忽略,期望保存旧数据。1.4如何选择阻塞队列阿里内部开源规范明确禁止使用无界队列。如果使用无界队列,任务会无限制的提交到线程池,可能会造成内存溢出。如果使用无界队列,最大线程数参数将无效,因为永远不会创建超过核心线程数的线程。1.5线程池工厂ThreadFactorythreadFactory有什么实际使用,线程池工厂,在使用线程池的时候,强烈建议使用自己定义的线程工厂,这样线程池中的线程就可以命名了,这样就可以使用jsatck命令查看线程栈,可以快速识别出对应的线程。1.6keepAliveTime参数的作用keepAliveTime:通俗地说,该参数表示线程的最大空闲时间,即线程不执行任务时可以存活的时间。默认情况下,该参数仅适用于超过核心线程数(corePoolSize)的线程。通过设置allowCoreThreadTimeOut为true,核心线程数也会因为空闲而关闭。2.如何为线程池设置合适的线程目前,根据我看到的一些开源框架,设置多少线程通常是根据应用的类型:IO密集型,CPU密集型。IO-intensive通常设置为2n+1,其中n为CPU核心数CPU-intensive通常设置为n+1。实际情况往往更复杂,不会按照这个来设置。上述公式通常适用于netty、dubbo等底层通信框架,通常参照上述标准设置。实际业务开发中如何为一个线程池设置合适的线程?其实对于IO密集型的应用,网上还有一个公式:线程数=CPU核数/(1-阻塞系数)引入阻塞系数这个概念一般在0.8到0.9之间。在我们的业务开发中,基本上是IO密集型的,因为我们经常操作数据库,访问redis、es等存储组件,涉及到磁盘IO和网络IO。什么样的场景是CPU密集型的?纯计算,比如计算pi的位数,当然我们基本接触不到。对于IO密集型,可以考虑多设置线程。主要目的是增加IO的并发。对于CPU密集型,不宜设置过多的线程,因为会引起线程切换,从而造成性能损失。下面我们通过一个实际场景来说明如何设置线程数。MQ消费者部署在4C8G机器上。在RocketMQ的实现中,消费者也是使用线程池来消费线程。如何设置线程数?如果采用2n+1的公式,线程数设置为9,但是在我们的实践中,我们发现如果增加线程数,消息处理能力会有明显的提升,说明2n+1不适合业务场景。如果线程数=CPU核数/(1-阻塞系数),则阻塞系数为0.8,线程数为20。阻塞系数为0.9,线程数约为40。我假设线程数是20,如果发现数据库的操作时间比较长,此时可以继续增加阻塞系数,从而增加线程数。那么如何判断需要增加线程呢?其实我们可以使用jstack命令查看进程的线程栈。如果我们发现线程池中大部分线程都在等待获取任务,说明线程是充足的,如下图所示:如果有部分线程在运行,可以继续适当增加线程数.