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

简介: 面试官:你是如何评估一个线程池需要设置多少个线程

Java并发编程是大厂第一轮面试中的高频面试题,而线程池又是其中的典型代表,本文将梳理关于线程池的工作机制,并提出灵魂之问:你对线程池的工作机制这么了解,那你在工作中是如何判断一个线程池需要创建多少个线程的呢?


1、线程池基本工作原理与面试指南


1.1 java线程池的核心属性


JAVA 线程池的核心属性如下:


  • int corePoolSize
    核心线程数
  • int maximumPoolSize
    线程池最大线程数
  • long keepAliveTime
    线程保持活跃的时间
  • TimeUnit unit
    keepAliveTime 的时间单位
  • BlockingQueue< Runnable > workQueue
    任务挤压队列
  • ThreadFactory threadFactory
    线程创建工厂类
  • RejectedExecutionHandler handler
    拒绝策略


1.2 向线程池提交任务时线程创建过程


那当用户向线程池提交一个任务的时候,线程池会如何创建线程呢?


  1. 首先线程池会判断当前已创建的线程是否小于 corePoolSize  (核心线程数),如果小于,则无论已创建的线程是否空闲,都会选择创建一个新的线程来执行该任务,直到已创建的线程等于核心线程数
  2. 当线程池中已创建的线程数等于核心核心线程数时,用户继续向线程池提交任务时,此时会先判断任务队列是否已满:
    1)如果任务队列未满,则将任务放入队列中。
    2)如果任务队列已满,则判断当前线程数量是否超过了最大线程数量,如果未超过,则创建一个新的线程来执行该任务,如果线程池已创建的线程数量等最大线程数,则执行拒绝策略。


 温馨提示:所以如果线程池使用的队列无界队列,最大线程数会变的没有意义。


1.3 线程池的拒绝策略、使用场景


JUC 默认提供了如下拒绝策略:


  • AbortPolicy
    拒绝,直接抛出 RejectedExecutionException,默认值。
  • CallerRunsPolicy
    由调用线程直接运行任务的 run 方法,即异步转同步。
  • DiscardOldestPolicy
     丢弃任务队列中最先进入的任务。
  • DiscardPolicy
      拒绝了,就不执行,“当没事人事”样。


拒绝策略触发的条件:线程池使用的是有界任务队列时,才有可能被触发,当队列已满,并且线程池创建的线程已经达到了最大允许的线程池时。


默认情况下,通常使用 AbortPolicy 即可


CallerRunsPolicy 异步转同步在出现拒绝的情况下其实意义不大,没有想出其合适的场景,因为需要执行拒绝策略的时候,已经处理变慢了,再同步执行任务,只会增加服务器的负载,不利于恢复问题。


DiscardOldestPolicy 这种策略,通常用于类似记录轨迹,偶尔丢失点数据没关系,但希望最新的数据能得到保存。


DiscardPolicy 策略,通常用来异步打印日志,直接忽略不执行,期望保存旧的数据。


1.4 如何选择阻塞队列


阿里内部的开源规范明确禁止使用无界队列,如果使用无界队列,任务会不受限制的往线程池中提交,有可能造成内存溢出。


如果使用无界队列,最大线程数这个参数将会失效,因为永远也不会创建多于核心线程数量的线程。


1.5 线程池工厂有何实际用处


ThreadFactory threadFactory,线程池工厂,在使用线程池时,强烈推荐使用自己定义的线程工厂,这样能为线程池中的线程进行命名,方便跟大家使用 jsatck 命令查看线程栈时,能快速识别对应的线程。


1.6 keepAliveTime参数的作用


keepAliveTime :通俗点来说,这个参数表示线程的最大空闲时间,即如果线程没有在执行任务,能存活的时间。


默认情况下,该参数只针对超过核心线程数(corePoolSize) 的线程,可通过将allowCoreThreadTimeOut设置为true,则核心线程数也会因为空闲而被关闭。


2、如何为线程池设置合适的线程


目前根据我看过的一些开源框架,设置多少个线程数量通常是根据应用的类型:IO密集型、CPU密集型。


  • IO密集型通常设置为2n+1,其中n为CPU核数
  • CPU密集型通常设置为 n+1。


实际情况往往复杂的多,并不会按照这个进行设置,上面的公式通常适合框架类,例如netty,dubbo这种底层通讯框架通常会参考上述标准进行设置。


关于在实际业务开发中,如何为一个线程池设置合适的线程呢?


其实对于IO密集型类型的应用,网上还有一个公式:线程数 = CPU核心数/(1-阻塞系数)

引入了阻塞系数的概念,一般为0.8~0.9之间,


在我们的业务开发中,基本上都是IO密集型,因为往往都会去操作数据库,访问redis,es等存储型组件,涉及到磁盘IO,网络IO。


那什么场景下是CPU密集型呢?纯计算类,例如计算圆周率的位数,当然我们基本接触不到。


IO密集型,可以考虑多设置一些线程,主要目的是可以增加IO的并发度,CPU密集型不宜设置过多线程,因为是会造成线程切换,反而损耗性能。


接下来我们以一个实际的场景来说明如何设置线程数量。


一个4C8G的机器上部署了一个MQ消费者,在RocketMQ的实现中,消费端也是用一个线程池来消费线程的,那这个线程数要怎么设置呢?


如果按照 2n + 1 的公式,线程数设置为 9个,但在我们实践过程中发现如果增大线程数量,会显著提高消息的处理能力,说明 2n + 1 对于业务场景来说,并不太合适。


如果套用 线程数 = CPU核心数/(1-阻塞系数) 阻塞系数取 0.8 ,线程数为 20 。阻塞系数取 0.9,大概线程数40,20个线程数我觉得可以


如果我们发现数据库的操作耗时比较多,此时可以继续提高阻塞系数,从而增大线程数量。


那我们怎么判断需要增加更多线程呢?其实可以用jstack命令查看一下进程的线程栈,如果发现线程池中大部分线程都处于等待获取任务,则说明线程够用,如下图所示:


e89157712af787e99cbd0b0657528ea7.png

如果大部分线程都处于运行状态,可以继续适当调高线程数量。

相关文章
|
20天前
|
监控 安全 Java
在 Java 中使用线程池监控以及动态调整线程池时需要注意什么?
【10月更文挑战第22天】在进行线程池的监控和动态调整时,要综合考虑多方面的因素,谨慎操作,以确保线程池能够高效、稳定地运行,满足业务的需求。
97 38
|
18天前
|
Java
线程池内部机制:线程的保活与回收策略
【10月更文挑战第24天】 线程池是现代并发编程中管理线程资源的一种高效机制。它不仅能够复用线程,减少创建和销毁线程的开销,还能有效控制并发线程的数量,提高系统资源的利用率。本文将深入探讨线程池中线程的保活和回收机制,帮助你更好地理解和使用线程池。
43 2
|
20天前
|
Prometheus 监控 Cloud Native
JAVA线程池监控以及动态调整线程池
【10月更文挑战第22天】在 Java 中,线程池的监控和动态调整是非常重要的,它可以帮助我们更好地管理系统资源,提高应用的性能和稳定性。
55 4
|
20天前
|
Prometheus 监控 Cloud Native
在 Java 中,如何使用线程池监控以及动态调整线程池?
【10月更文挑战第22天】线程池的监控和动态调整是一项重要的任务,需要我们结合具体的应用场景和需求,选择合适的方法和策略,以确保线程池始终处于最优状态,提高系统的性能和稳定性。
87 2
|
1月前
|
Java
线程池设置原则
线程池设置原则
|
1月前
|
Dubbo Java 应用服务中间件
剖析Tomcat线程池与JDK线程池的区别和联系!
剖析Tomcat线程池与JDK线程池的区别和联系!
103 0
剖析Tomcat线程池与JDK线程池的区别和联系!
|
1月前
|
设计模式 Java 物联网
【多线程-从零开始-玖】内核态,用户态,线程池的参数、使用方法详解
【多线程-从零开始-玖】内核态,用户态,线程池的参数、使用方法详解
58 0
|
3月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
7天前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
9天前
|
存储 缓存 Java
大厂面试必看!Java基本数据类型和包装类的那些坑
本文介绍了Java中的基本数据类型和包装类,包括整数类型、浮点数类型、字符类型和布尔类型。详细讲解了每种类型的特性和应用场景,并探讨了包装类的引入原因、装箱与拆箱机制以及缓存机制。最后总结了面试中常见的相关考点,帮助读者更好地理解和应对面试中的问题。
33 4

相关实验场景

更多