尼恩说在前面
在40岁老架构师 尼恩的读者交流群(50+)中,最近有小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、shein 希音、百度、网易的面试资格,遇到很多很重要的面试题:
如何确定系统的最佳线程数?
小伙伴 没有回答好,导致面试挂了。
这个是一个非常常见的面试题,考察的是线程池的基本功。
如何才能回答得很漂亮,才能 让面试官刮目相看、口水直流呢?这里,尼恩给大家做一下系统化、体系化的梳理,让面试官爱到 “不能自已、口水直流”,然后帮大家 实现 ”offer自由”。
当然,这道面试题,以及参考答案,也会收入咱们的 《尼恩Java面试宝典》V174版本PDF集群,供后面的小伙伴参考,提升大家的 3高 架构、设计、开发水平。
注:本文以 PDF 持续更新,最新尼恩 架构笔记、面试题 的PDF文件,请关注本公众号【技术自由圈】获取。
如何确定系统的最佳线程数?
首先看编程规范中, 有两个很重要的,与线程有关的需要强制执行的规范:
规范一:【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
说明: Java线程的创建非常昂贵,需要JVM和OS(操作系统)配合完成大量的工作:
1)消耗内存资源:必须为线程堆栈分配和初始化大量内存块,其中包含至少1MB的栈内存。
2)消耗CPU资源:需要进行系统调用,以便在OS(操作系统)中创建和注册内核线程,大量内核线程调度会导致CPU上下文过度切换。
所以,Java高并发应用频繁创建和销毁线程的操作将是非常低效的,而且是不被编程规范所允许的。
如何降低Java线程的创建成本?必须使用到线程池。使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
以上的内容,在尼恩的 《Java 高并发核心编程 卷2》 进行了详细介绍。
规范二:【强制】线程池不允许使用Executors去创建快捷线程池 ,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors返回的线程池对象的弊端如下:
- FixedThreadPool和SingleThreadPool: 允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
- CachedThreadPool和ScheduledThreadPool: 允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
通过以上规范,说明我们应用中,需要用自定义线程池。 然而,由于构造一个线程池竟然有7个参数
7个重要参数中,最为重要的三个是:核心,最大线程数量, BlockingQueue。前两个参数和线程数量有关系, 后一个和内存资源消耗有关。
线程数设置太少或者阻塞队列太小, 会导致大量任务被拒绝,抛出RejectedExecutionException,触发线上的接口降级,用户体验很差。
二线程数设置太多或者阻塞队列太长,会导致资源消费高而有效负荷很小, 特别是阻塞队列设置过长,会导致频繁FullGC,甚至OOM。
确定系统的最佳线程数的3个step
如何确定系统的最佳线程数,大体上分三步:
第一步,理论预估;
第二步,压测验证;
第三步,监控调整。
这也是尼恩给大家归纳的,最为理想的: 可监控/可弹性的 线程池模式
step1: 完成线程数的理论预估 (设计阶段)
在尼恩的 《Java 高并发核心编程 卷2》 进行了详细介绍。
首先,按照任务类型对线程池进行分类, 分为三类,具体如下图:
具体,请参见在尼恩的 《Java 高并发核心编程 卷2》 1.7.1 小节。
第一类:IO 密集型线程池线程数预估
线程数就是 CPU的核数的2倍。
具体,请参见在尼恩的 《Java 高并发核心编程 卷2》 1.7.2 小节。
第二类:CPU密集线程池线程数预估
CPU密集型任务并行执行的数量应当等于CPU的核心数, 线程数就是 CPU的核数
具体,请参见在尼恩的 《Java 高并发核心编程 卷2》 1.7.3小节。
第三类:混合型线程池线程数预估
混合型线程池线程数预估, 参考下面的的公式:
最佳线程数 = ((线程等待时间 + 线程 CPU 时间) / 线程 CPU 时间 ) * CPU 核数
具体,请参见在尼恩的 《Java 高并发核心编程 卷2》 1.7.4小节。
step2: 完成线程数的压测验证 (设计阶段)
过少的线程会造成任务拒绝,业务降级。
过多的线程会造成,额外的内存开销CPU开销,甚至会导致OOM。
所以,合理的线程池线程数,才是王道。
在设计阶段完成了step1的线程数的理论预估之后, 那么我们的理论值就出来了。
如何做验证呢? 这里需要 压测。
根据公式:
服务器端最佳线程数量=((线程等待时间+线程cpu时间)/线程cpu时间) * cpu数量
前面线程等待时间,线程cpu时间都是 预估的 ,都是要验证的。
首先通过用户慢慢递增来进行性能压测,观察QPS。 持续大的增加用户数, 压测出最大的吞吐量。
然后再 收集 最大的吞吐量场景的 线程等待时间,线程cpu时间, 再计算出最佳线程数。
step3: 完成线程数的线上调整 (生产阶段)
压测的场景,是有限的。 而线上的业务, 是复杂的,多样的。
由于系统运行过程中存在的不确定性,很难一劳永逸地规划一个合理的线程数。
所以,需要进行生产阶段线程数的两个目标:
- 可监控预警
- 可在线调整
第一个维度:可监控预警
第二个维度:可在线调整
在线动态调整实操:结合Nacos 实现动态化线程池
优秀的动态化线程池轮子,主要有:
- Hippo4J
- dynamic-tp
如果线上使用,可以使用这些轮子项目。
但是尼恩的是[技术自由圈]一个实战社群,必须自己从0到1,去撸一把代码,提升自己的水平。
1.结合Nacos 实现动态化线程池架构
结合Nacos 实现动态化线程池的参数在线调整,架构如下:
2.Nacos 上的配置如下:
3.线程池配置和nacos配置变更监听
4.线程池配置的动态刷新
5.LinkedBlockingQueue 实现resize
LinkedBlockingQueue 不支持 resize, 需要重新定制。自定义可以扩容的 LinkedBlockingQueue ,结构如下:
这里采用的是读写锁,对capacity 的设置,进行线程安全 保护:
读写锁的使用如下:
通过对capacity的安全修改,以达到动态扩展目的。
其他代码和LinkedBlockingQueue代码一致。
在线动态监控实操:结合PGA实现Metric采集和预警
先把架构图画出来,大致如下:
具体的实现细节,留到 《尼恩java面试宝典》 配套视频里边,慢慢介绍哈。
《尼恩java面试宝典》 ,即将一个专题一个专题的开始录制视频了。
说在最后
线程相关的面试题,是非常常见的面试题。
以上的内容,如果大家能对答如流,如数家珍,基本上 面试官会被你 震惊到、吸引到。最终,让面试官爱到 “不能自已、口水直流”。offer, 也就来了。
在面试之前,建议大家系统化的刷一波 5000页《尼恩Java面试宝典》V174,在刷题过程中,如果有啥问题,大家可以来 找 40岁老架构师尼恩交流。
另外,如果没有面试机会,可以找尼恩来帮扶、领路。
尼恩已经指导了大量的就业困难的小伙伴上岸,前段时间,帮助一个40岁+就业困难小伙伴拿到了一个年薪100W的offer,小伙伴实现了 逆天改命 。
尼恩技术圣经系列PDF
- 《NIO圣经:一次穿透NIO、Selector、Epoll底层原理》
- 《Docker圣经:大白话说Docker底层原理,6W字实现Docker自由》
- 《K8S学习圣经:大白话说K8S底层原理,14W字实现K8S自由》
- 《SpringCloud Alibaba 学习圣经,10万字实现SpringCloud 自由》
- 《大数据HBase学习圣经:一本书实现HBase学习自由》
- 《大数据Flink学习圣经:一本书实现大数据Flink自由》
- 《响应式圣经:10W字,实现Spring响应式编程自由》
- 《Go学习圣经:Go语言实现高并发CRUD业务开发》
……完整版尼恩技术圣经PDF集群,请找尼恩领取
《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》PDF,请到下面公号【技术自由圈】取↓↓↓