合理配置线程池要结合任务类型(CPU密集还是IO密集)、业务特点(ToB还是ToC)、机器资源来综合考量。
评估是否需要上并行
-
任务的可分解性:任务是否能被拆分成多个可以独立执行的子单元?如果任务本身是严格串行的,并行化没有意义。
-
计算资源:是否有足够的CPU核心来支持并行执行?在单核CPU上多线程主要是为了并发(应对I/O等待)、而不是真正的并行计算。
-
任务类型:
-
CPU密集型任务:这类任务几乎不涉及IO、计算耗时高、适合使用较小的线程池、线程数一般为 CPU核心数+1。此时每个线程都在占用CPU执行计算、多了反而增加上下文切换的成本、得不偿失。这类任务适合并行化以充分利用多核CPU
-
IO密集型任务:此类任务会频繁进行网络、磁盘等IO操作、线程经常阻塞等待IO完成。这时可以适当将线程数设2N.这类任务通过并发可以提高系统吞吐量和响应性\因为线程可以在等待I/O时释放CPU给其他任务
-
然后如果是IO密集型
-
虚拟线程与CPU密集型任务:(ForkJoin任务窃取底层解决虚拟线程IO阻塞时的切换)
-
轻量级与高并发: 虚拟线程创建和切换的成本极低、可以轻松创建数百万个。这使得它们非常适合处理大量并发的、大部分时间在等待I/O(如网络请求、数据库查询、文件读写)的任务。
-
I/O阻塞处理: 当虚拟线程遇到I/O阻塞时、它会从其底层的平台载体线程上卸载。这个平台载体线程并不会被阻塞、而是可以立即去执行另一个准备就绪的虚拟线程。当I/O操作完成后、原来的虚拟线程会重新提交给调度器,等待被某个可用的载体线程挂载并继续执行
-
高效的平台线程利用: 这种机制使得少量的平台载体线程(通常数量与CPU核心数相关)能够高效地管理和执行海量的并发I/O任务、避免了传统平台线程因I/O阻塞而大量闲置的问题、从而极大提高了系统吞吐量
ToB 与 ToC 业务对线程池参数的影响
-
ToC (面向消费者) 应用:
-
特点: 通常要求快速响应、用户体验至上、并发用户量可能波动大。
-
线程池配置倾向:
-
maximumPoolSize 可以设置得相对较大、以应对突发流量。
-
workQueue 的长度可能不宜过长(或者使用 SynchronousQueue)、避免请求积压过多导致用户感知到的延迟增加。如果队列太长、用户可能已经不耐烦了
-
拒绝策略:倾向于快速失败(如 ThreadPoolExecutor.AbortPolicy,抛出异常)。这使得客户端或上游服务可以更快地得到反馈(例如,知道请求失败)、从而可以进行重试、降级处理或向用户展示错误提示、而不是让用户长时间无响应地等待。
-
-
总的来说可以:阻塞队列长度可以少点、最大线程数多点->优先保证响应速度和应对流量弹性的
-
-
ToB (面向企业) 应用:
-
特点: 可能有大量稳定的并发任务、对整体吞吐量和数据处理的可靠性和稳定性要求高。单个任务的处理时间可能较长。任务数量多、传送数据量大
-
-
线程池配置倾向:
-
corePoolSize 和 maximumPoolSize 、两者可能设置得更接近、或者 maximumPoolSize 根据预估的持续平均并发任务数来设定、而不是为了应对极端短暂的峰值。目标是维持一个相对稳定的处理能力。
-
workQueue 可以设置得相对较长(例如使用 LinkedBlockingQueue,其默认容量接近无限、或者一个容量较大的 ArrayBlockingQueue)。这样可以缓冲大量任务、确保在系统处理能力暂时跟不上任务到达速度时、任务不会被直接拒绝而丢失。
-
拒绝策略倾向于更保守、更能保证任务不丢失的策略。例如:
-
ThreadPoolExecutor.CallerRunsPolicy:让提交任务的线程自己来执行这个任务。这既能保证任务被执行、也能间接起到减缓任务提交速度的背压效果。
-
自定义拒绝策略,将任务持久化到磁盘或其他地方,后续再进行处理。
-
总的来说可以阻塞队列长度可以多点、最大线程数:maximumPoolSize
的设置不追求像ToC那样为了应对极端峰值而设得特别大、而是更多地基于预期的持续并发处理需求和系统资源(如CPU、内存、下游依赖的处理能力)来设定一个合理且相对稳定的值。
结合实际资源来设定
-
比如内存较小的机器不适合设置过多线程。
-
对于业务流量不稳定的场景、可以考虑使用 动态线程池(如 ThreadPoolExecutor 中允许设置核心线程数和最大线程数、并结合拒绝策略、空闲线程存活时间等)、根据负载动态扩展线程数、提升资源利用率和系统弹性。
拓展题:
题目1.
首先问:如果又想要并行优化效率又不想多线程打垮对面的系统,该怎么设计一个方案,就是又要快又要成功发送。
回答:可以Kafka的消费者控制消费逻辑、让对方控制Offerset提交、这样子就不会打崩别人的系统了。
继续问:但是对方已经提供了接口,不太可能给我们单独做一个消费者去消费
回答:发送方用Kafka消息队列控制发送的量,利用Kafka的事务和自定义批量发送逻辑来提高效率
题目2:
线程的创建和销毁有开销、线程过多效率会很低、多线程的情况下容易发生OOM的问题
题目3:
如何排查OOM