线程池线程数如何设置
在实际工作和面试中,线程池线程个数的设置是一个常见而又复杂的问题。我们通常在资料中找到一些经典的回答,例如判断任务是CPU密集型还是IO密集型,或者参考《Java并发编程实战》一书的建议:
1.判断任务是CPU密集型还是IO密集型(其中N表示CPU的核心数量):
- CPU密集型,线程池大小设置为N+1。
- IO密集型,线程池大小设置为2N+1。
然而,在实际业务中,任务往往涉及到复杂的计算和等待操作,而且一个服务通常不仅执行单一任务。
2.《Java并发编程实战》的推荐设置:
- Ncpu表示CPU的数量。
- Ucpu表示期望的CPU的使用率。
- W/C表示等待时间与计算时间的比例。
比如说我们通过监控判断接口CPU计算时间是20ms,等待时间是80ms,期望CPU使用率是80%,那么根据公式得出:
线程数=CPU个数8*CPU使用率0.8*(1+(等待时间80ms/计算时间20ms))=32
在实际应用中,由于业务场景的复杂性,一个服务可能涉及多种操作,包括内存读写、计算、I/O等。因此,仅仅通过公式计算线程数可能过于理想化。当我们这样回答时,面试官显然是不会满意的。
业务程序中如何量化线程数?
此时项目中需要一个业务场景的多线程(或者线程池)来异步/并行执行业务流程。
以一个常规的接口处理为例,可能涉及以下流程:
该接口涉及以下步骤:
- 参数解析、参数校验
- 缓存查询数据,如果查询到直接返回了
- 如果缓存没查到数据,调用数据库查询数据
- 调用RPC接口查询数据
- 业务逻辑本地计算处理
- 将计算后的数据更新到缓存
- 最后进行参数组装并返回
这个接口中,步骤2、3、4、6都涉及IO等待,其余步骤会涉及CPU计算。需要注意的是步骤2查询到缓存后直接返回了,不会执行后面的流程。
线程数计算误区
如果我们按照上面的公式来规划线程数的话,误差一定会很大。
做业务开发时,我们通常选择SpringBoot
作为基础框架,使用Tomcat
容器、druid
连接池、并采用G1
垃圾回收器。
在设置线程数时,我们常常受到误导。例如,一个主机上可能不仅部署了一个进程,一个进程中还有许多运行中的线程,同时druid
、JVM
和G1
也都有各自的线程池和后台线程。这些线程都在当前进程、当前主机上运行,也占用CPU资源。
此外,CPU执行时会涉及CPU时间片轮转和线程切换,而高峰期数据库和RPC接口的响应时间也会有所不同。
如何设置线程数?
那么到底如何设置线程数呢?这个没有标准答案,我们一定要结合场景,跟面试官讲清楚。最好通过公式初步设置一个大概值,然后通过测试去找到一个最合适的线程数。同时,线程池最好支持动态调整,这样即时有问题,我们也能灵活的进行调整。
这样的解释能够更好地向面试官传达我们对于线程数设置的理解和灵活应对业务复杂性的能力。