禅与计算机 禅与计算机
首页
  • Java基础

    • 聊一聊java一些核心知识点
    • 聊聊java面向对象核心知识点
    • 聊聊Java中的异常
    • 聊聊Java中的常用类String
    • 万字长文带你细聊Java注解本质
    • 来聊聊Java的反射机制
    • 深入解析Java泛型的魅力与机制
    • Java集合框架深度解析与面试指南
    • Java常用集合类HashMap深度解析
    • LinkedHashMap源码到面试题的全解析
    • 深入解析CopyOnWriteArrayList的工作机制
    • Java基础IO总结
    • Java三大IO模型小结
    • Java BIO NIO AIO详解
    • Java进阶NIO之IO多路复用详解
    • Java8流式编程入门
    • 一文速通lambda与函数式编程
    • Java8函数式方法引用最佳实践
  • Java并发编程

    • Java并发编程基础小结
    • 深入理解Java中的final关键字
    • 浅谈Java并发安全发布技术
    • 浅谈Java并发编程中断的哲学
    • Java线程池知识点小结
    • 浅谈Java线程池中拒绝策略与流控的艺术
    • synchronized关键字使用指南
    • 深入源码解析synchronized关键字
    • 详解JUC包下的锁
    • 详解并发编程中的CAS原子类
    • LongAdder源码分析
    • AQS源码解析
    • 深入剖析Java并发编程中的死锁问题
    • Java并发容器总结
    • 详解Java并发编程volatile关键字
    • 并发编程ThreadLocal必知必会
    • CompletableFuture基础实践小结
    • CompletableFuture异步多任务最佳实践
    • 硬核详解FutureTask设计与实现
    • 线程池大小设置的底层逻辑与场景化方案
    • 来聊一个有趣的限流器RateLimiter
  • JVM相关

    • 从零开始掌握 JVM
    • JVM核心知识点小结
    • JVM指令集概览:基础与应用
    • JVM类加载器深度解析
    • JVM方法区深度解析
    • Java内存模型JMM详解
    • Java对象大小的精确计算方法
    • 逃逸分析在Java中的应用与优化
    • 从零开始理解JVM的JIT编译机制
    • G1垃圾回收器:原理详解与调优指南
    • JVM故障排查实战指南
    • JVM内存问题排错最佳实践
    • JVM内存溢出排查指南
    • 简明的Arthas使用教程
    • 简明的Arthas配置及基础运维教程
    • 基于Arthas Idea的JVM故障排查与指令生成
    • 基于arthas量化监控诊断java应用方法论与实践
    • 深入剖析arthas技术原理
  • 深入理解Spring框架

    • Spring 核心知识点全面解析
    • Spring核心功能IOC详解
    • Spring AOP 深度剖析与实践
    • Spring 三级缓存机制深度解析
    • 深入 Spring 源码,剖析设计模式的落地实践
    • 探索 Spring 事务的奥秘
    • 深入解析Spring Bean的生命周期管理
    • 解读 Spring Boot 核心知识点
    • Spring Boot 启动优化实战:1分钟到13秒的排查与优化之路
    • Spring Boot自动装配原理及实践
    • 一文快速上手Sharding-JDBC
    • sharding-jdbc如何实现分页查询
    • 基于DynamicDataSource整合分库分表框架Shardingsphere
  • 计算机组成原理

    • 计算机硬件知识小结
    • CPU核心知识点小结
    • 浅谈CPU流水线的艺术
    • 从Java程序员视角聊聊CPU缓存
    • CPU任务调度和伪共享问题小结
    • CPU MESI缓存一致性协议
    • CPU内存管理机制
    • 内存深度解析
    • 磁盘存储原理
    • 详解计算机启动步骤
    • CPU南北桥架构与发展史
    • CPU中断机制与硬件交互详解
  • 操作系统

    • 如何实现一个高性能服务器
    • Linux文件结构与文件权限
    • Linux常见压缩指令小结
    • Linux核心系统调用详解
    • Linux进程管理
    • Linux线程管理
    • 进程与线程深度解析
    • Linux进程间通信机制
    • 零拷贝技术原理与实践
    • CPU缓存一致性问题深度解析
    • IO任务与CPU调度艺术
  • 计算机网络

    • 网卡通信原理详解
    • 网卡数据包处理指南
    • 基于抓包详解TCP协议
  • 编码最佳实践

    • 浅谈现代软件工程TDD最佳实践
    • 浅谈TDD模式下并发程序设计与实现
    • 面向AI编程新范式Trae后端开发环境搭建与实践
    • 基于提示词工程的Redis签到功能开发实践
    • 基于Vibe Coding的Redis分页查询实现
    • 告别AI无效对话:资深工程师的提示词设计最佳实践
  • 实用技巧与配置

    • Mac常用快捷键与效率插件指南
    • Keynote技术科普短视频制作全攻略
  • 写作

    • 写好技术博客的5大核心原则:从认知科学到AI工具的全流程指南
  • 开发工具

    • IDEA配置详解与高效使用指南
  • Nodejs
  • 博客搭建
  • Redis

    • Redis核心知识小结
    • 解锁Redis发布订阅模式
    • 掌握Redis事务
    • Redis主从复制技术
    • Redis的哨兵模式详解
    • 深度剖析Redisson分布式锁
    • 详解redis单线程设计思路
    • 来聊聊Redis所实现的Reactor模型
    • Redis RDB持久化源码深度解析
    • 来聊聊redis的AOF写入
    • 来聊聊Redis持久化AOF管道通信的设计
    • 来聊聊redis集群数据迁移
    • Redis SDS动态字符串深度解析
    • 高效索引的秘密:redis跳表设计与实现
    • 聊聊redis中的字典设计与实现
  • MySQL

    • MySQL基础知识点小结
    • 解读MySQL 索引基础
    • MySQL 索引进阶指南
    • 解读MySQL Explain关键字
    • 探秘 MySQL 锁:原理与实践
    • 详解MySQL重做日志redolog
    • 详解undoLog在MySQL MVCC中的运用
    • MySQL二进制日志binlog核心知识点
    • MySQL高效插入数据的最佳实践
    • MySQL分页查询优化指南
    • MySQL流式查询的奥秘与应用解析
    • 来聊聊分库分表
    • 来聊聊大厂常用的分布式ID生成方案
  • ElasticSearch

    • 从Lucene到Elasticsearch:进化之路
    • ES 基础使用指南
    • ElasticSearch如何写入一篇文档
    • 深入剖析Elasticsearch文档读取原理
    • 聊聊ElasticSearch性能调优
    • Spring借助Easy-Es操作ES
  • Netty

    • 一文快速了解高性能网络通信框架Netty
    • Netty网络传输简记
    • 来聊聊Netty的ByteBuf
    • 来聊聊Netty消息发送的那些事
    • 解密Netty高性能之谜:NioEventLoop线程池阻塞分析
    • 详解Netty中的责任链Pipeline如何管理ChannelHandler
    • Netty Reactor模型常见知识点小结
    • Netty如何驾驭TCP流式传输?粘包拆包问题全解
    • Netty解码器源码解析
  • 消息队列

    • 一文快速入门消息队列
    • 消息队列RocketMQ入门指南
    • 基于RocketMQ实现分布式事务
    • RocketMQ容器化最佳实践
    • RocketMQ常见问题与深度解析
    • Kafka快速安装与使用指南
  • Nginx

    • Linux下的nginx安装
    • Nginx基础入门总结
    • Nginx核心指令小结
    • Nginx进程结构与核心模块初探
    • Nginx应用进阶HTTP核心模块配置
    • Nginx缓存及HTTPS配置小记
    • nginx高可用实践简记
    • Nginx性能优化
  • 微服务基础

    • 微服务基础知识小结
    • 分布式事务核心概念小结
    • OpenFeign核心知识小结
    • 微服务组件Gateway核心使用小结
    • 分布式事务Seata实践
    • 用 Docker Compose 完成 Seata 的整合部署
  • Nacos

    • Nacos服务注册原理全解析
    • Nacos服务订阅流程全解析
    • Nacos服务变更推送流程全解析
    • 深入解析SpringCloud负载均衡器Loadbalancer
    • Nacos源码环境搭建与调试指南
  • Seata

    • 深度剖析Seata源码
  • Docker部署

    • 一文快速掌握docker的理念和基本使用
    • 使用docker编排容器
    • 基于docker-compose部署微服务基本环境
    • 基于docker容器化部署微服务
    • Gateway全局异常处理及请求响应监控
    • Docker图形化界面工具Portainer最佳实践
  • Go基础

    • 一文带你速通Go语言基础语法
    • 一文快速掌握Go语言切片
    • 来聊聊go语言的hashMap
    • 一文速通go语言类型系统
    • 浅谈Go语言中的面向对象
    • go语言是如何实现协程的
    • 聊聊go语言中的GMP模型
    • 极简的go语言channel入门
    • 聊聊go语言基于epoll的网络并发实现
    • 写给Java开发的Go语言协程实践
  • mini-redis实战

    • 来聊聊我用go手写redis这件事
    • mini-redis如何解析处理客户端请求
    • 实现mini-redis字符串操作
    • 硬核复刻redis底层双向链表核心实现
    • 动手复刻redis之go语言下的字典的设计与落地
    • Go 语言下的 Redis 跳表设计与实现
    • Go 语言版 Redis 有序集合指令复刻探索
  • 项目编排

    • Spring脚手架创建简记
    • Spring脚手架集成分页插件
    • Spring脚手架集成校验框架
    • maven父子模块两种搭建方式简记
    • SpringBoot+Vue3前后端快速整合入门
    • 来聊聊Java项目分层规范
  • 场景设计

    • Java实现文件分片上传
    • 基于时间缓存优化浏览器轮询阻塞问题
    • 基于EasyExcel实现高效导出
    • 10亿数据高效插入MySQL最佳方案
    • 从开源框架中学习那些实用的位运算技巧
  • CI/CD

    • 基于NETAPP实现内网穿透
    • 基于Gitee实现Jenkins自动化部署SpringBoot项目
    • Jenkins离线安装部署教程简记
    • 基于Nexus搭建Maven私服基础入门
    • 基于内网的Jenkins整合gitlab综合方案简记
  • 监控方法论

    • SpringBoot集成Prometheus与Grafana监控
    • Java监控度量Micrometer全解析
    • 从 micrometer计量器角度快速上手promQL
    • 硬核安利一个监控告警开源项目Nightingale
  • Spring AI

    • Spring AI Alibaba深度实战:一文掌握智能体开发全流程
    • Spring AI Alibaba实战:JVM监控诊断Arthas Agent的工程化构建与最佳实践
  • 大模型评测

    • M2.7 真能打!我用两个真实场景测了测,结果有点意外
    • Qoder JetBrains插件评测:祖传代码重构与接口优化实战
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

sharkchili

计算机禅修者
首页
  • Java基础

    • 聊一聊java一些核心知识点
    • 聊聊java面向对象核心知识点
    • 聊聊Java中的异常
    • 聊聊Java中的常用类String
    • 万字长文带你细聊Java注解本质
    • 来聊聊Java的反射机制
    • 深入解析Java泛型的魅力与机制
    • Java集合框架深度解析与面试指南
    • Java常用集合类HashMap深度解析
    • LinkedHashMap源码到面试题的全解析
    • 深入解析CopyOnWriteArrayList的工作机制
    • Java基础IO总结
    • Java三大IO模型小结
    • Java BIO NIO AIO详解
    • Java进阶NIO之IO多路复用详解
    • Java8流式编程入门
    • 一文速通lambda与函数式编程
    • Java8函数式方法引用最佳实践
  • Java并发编程

    • Java并发编程基础小结
    • 深入理解Java中的final关键字
    • 浅谈Java并发安全发布技术
    • 浅谈Java并发编程中断的哲学
    • Java线程池知识点小结
    • 浅谈Java线程池中拒绝策略与流控的艺术
    • synchronized关键字使用指南
    • 深入源码解析synchronized关键字
    • 详解JUC包下的锁
    • 详解并发编程中的CAS原子类
    • LongAdder源码分析
    • AQS源码解析
    • 深入剖析Java并发编程中的死锁问题
    • Java并发容器总结
    • 详解Java并发编程volatile关键字
    • 并发编程ThreadLocal必知必会
    • CompletableFuture基础实践小结
    • CompletableFuture异步多任务最佳实践
    • 硬核详解FutureTask设计与实现
    • 线程池大小设置的底层逻辑与场景化方案
    • 来聊一个有趣的限流器RateLimiter
  • JVM相关

    • 从零开始掌握 JVM
    • JVM核心知识点小结
    • JVM指令集概览:基础与应用
    • JVM类加载器深度解析
    • JVM方法区深度解析
    • Java内存模型JMM详解
    • Java对象大小的精确计算方法
    • 逃逸分析在Java中的应用与优化
    • 从零开始理解JVM的JIT编译机制
    • G1垃圾回收器:原理详解与调优指南
    • JVM故障排查实战指南
    • JVM内存问题排错最佳实践
    • JVM内存溢出排查指南
    • 简明的Arthas使用教程
    • 简明的Arthas配置及基础运维教程
    • 基于Arthas Idea的JVM故障排查与指令生成
    • 基于arthas量化监控诊断java应用方法论与实践
    • 深入剖析arthas技术原理
  • 深入理解Spring框架

    • Spring 核心知识点全面解析
    • Spring核心功能IOC详解
    • Spring AOP 深度剖析与实践
    • Spring 三级缓存机制深度解析
    • 深入 Spring 源码,剖析设计模式的落地实践
    • 探索 Spring 事务的奥秘
    • 深入解析Spring Bean的生命周期管理
    • 解读 Spring Boot 核心知识点
    • Spring Boot 启动优化实战:1分钟到13秒的排查与优化之路
    • Spring Boot自动装配原理及实践
    • 一文快速上手Sharding-JDBC
    • sharding-jdbc如何实现分页查询
    • 基于DynamicDataSource整合分库分表框架Shardingsphere
  • 计算机组成原理

    • 计算机硬件知识小结
    • CPU核心知识点小结
    • 浅谈CPU流水线的艺术
    • 从Java程序员视角聊聊CPU缓存
    • CPU任务调度和伪共享问题小结
    • CPU MESI缓存一致性协议
    • CPU内存管理机制
    • 内存深度解析
    • 磁盘存储原理
    • 详解计算机启动步骤
    • CPU南北桥架构与发展史
    • CPU中断机制与硬件交互详解
  • 操作系统

    • 如何实现一个高性能服务器
    • Linux文件结构与文件权限
    • Linux常见压缩指令小结
    • Linux核心系统调用详解
    • Linux进程管理
    • Linux线程管理
    • 进程与线程深度解析
    • Linux进程间通信机制
    • 零拷贝技术原理与实践
    • CPU缓存一致性问题深度解析
    • IO任务与CPU调度艺术
  • 计算机网络

    • 网卡通信原理详解
    • 网卡数据包处理指南
    • 基于抓包详解TCP协议
  • 编码最佳实践

    • 浅谈现代软件工程TDD最佳实践
    • 浅谈TDD模式下并发程序设计与实现
    • 面向AI编程新范式Trae后端开发环境搭建与实践
    • 基于提示词工程的Redis签到功能开发实践
    • 基于Vibe Coding的Redis分页查询实现
    • 告别AI无效对话:资深工程师的提示词设计最佳实践
  • 实用技巧与配置

    • Mac常用快捷键与效率插件指南
    • Keynote技术科普短视频制作全攻略
  • 写作

    • 写好技术博客的5大核心原则:从认知科学到AI工具的全流程指南
  • 开发工具

    • IDEA配置详解与高效使用指南
  • Nodejs
  • 博客搭建
  • Redis

    • Redis核心知识小结
    • 解锁Redis发布订阅模式
    • 掌握Redis事务
    • Redis主从复制技术
    • Redis的哨兵模式详解
    • 深度剖析Redisson分布式锁
    • 详解redis单线程设计思路
    • 来聊聊Redis所实现的Reactor模型
    • Redis RDB持久化源码深度解析
    • 来聊聊redis的AOF写入
    • 来聊聊Redis持久化AOF管道通信的设计
    • 来聊聊redis集群数据迁移
    • Redis SDS动态字符串深度解析
    • 高效索引的秘密:redis跳表设计与实现
    • 聊聊redis中的字典设计与实现
  • MySQL

    • MySQL基础知识点小结
    • 解读MySQL 索引基础
    • MySQL 索引进阶指南
    • 解读MySQL Explain关键字
    • 探秘 MySQL 锁:原理与实践
    • 详解MySQL重做日志redolog
    • 详解undoLog在MySQL MVCC中的运用
    • MySQL二进制日志binlog核心知识点
    • MySQL高效插入数据的最佳实践
    • MySQL分页查询优化指南
    • MySQL流式查询的奥秘与应用解析
    • 来聊聊分库分表
    • 来聊聊大厂常用的分布式ID生成方案
  • ElasticSearch

    • 从Lucene到Elasticsearch:进化之路
    • ES 基础使用指南
    • ElasticSearch如何写入一篇文档
    • 深入剖析Elasticsearch文档读取原理
    • 聊聊ElasticSearch性能调优
    • Spring借助Easy-Es操作ES
  • Netty

    • 一文快速了解高性能网络通信框架Netty
    • Netty网络传输简记
    • 来聊聊Netty的ByteBuf
    • 来聊聊Netty消息发送的那些事
    • 解密Netty高性能之谜:NioEventLoop线程池阻塞分析
    • 详解Netty中的责任链Pipeline如何管理ChannelHandler
    • Netty Reactor模型常见知识点小结
    • Netty如何驾驭TCP流式传输?粘包拆包问题全解
    • Netty解码器源码解析
  • 消息队列

    • 一文快速入门消息队列
    • 消息队列RocketMQ入门指南
    • 基于RocketMQ实现分布式事务
    • RocketMQ容器化最佳实践
    • RocketMQ常见问题与深度解析
    • Kafka快速安装与使用指南
  • Nginx

    • Linux下的nginx安装
    • Nginx基础入门总结
    • Nginx核心指令小结
    • Nginx进程结构与核心模块初探
    • Nginx应用进阶HTTP核心模块配置
    • Nginx缓存及HTTPS配置小记
    • nginx高可用实践简记
    • Nginx性能优化
  • 微服务基础

    • 微服务基础知识小结
    • 分布式事务核心概念小结
    • OpenFeign核心知识小结
    • 微服务组件Gateway核心使用小结
    • 分布式事务Seata实践
    • 用 Docker Compose 完成 Seata 的整合部署
  • Nacos

    • Nacos服务注册原理全解析
    • Nacos服务订阅流程全解析
    • Nacos服务变更推送流程全解析
    • 深入解析SpringCloud负载均衡器Loadbalancer
    • Nacos源码环境搭建与调试指南
  • Seata

    • 深度剖析Seata源码
  • Docker部署

    • 一文快速掌握docker的理念和基本使用
    • 使用docker编排容器
    • 基于docker-compose部署微服务基本环境
    • 基于docker容器化部署微服务
    • Gateway全局异常处理及请求响应监控
    • Docker图形化界面工具Portainer最佳实践
  • Go基础

    • 一文带你速通Go语言基础语法
    • 一文快速掌握Go语言切片
    • 来聊聊go语言的hashMap
    • 一文速通go语言类型系统
    • 浅谈Go语言中的面向对象
    • go语言是如何实现协程的
    • 聊聊go语言中的GMP模型
    • 极简的go语言channel入门
    • 聊聊go语言基于epoll的网络并发实现
    • 写给Java开发的Go语言协程实践
  • mini-redis实战

    • 来聊聊我用go手写redis这件事
    • mini-redis如何解析处理客户端请求
    • 实现mini-redis字符串操作
    • 硬核复刻redis底层双向链表核心实现
    • 动手复刻redis之go语言下的字典的设计与落地
    • Go 语言下的 Redis 跳表设计与实现
    • Go 语言版 Redis 有序集合指令复刻探索
  • 项目编排

    • Spring脚手架创建简记
    • Spring脚手架集成分页插件
    • Spring脚手架集成校验框架
    • maven父子模块两种搭建方式简记
    • SpringBoot+Vue3前后端快速整合入门
    • 来聊聊Java项目分层规范
  • 场景设计

    • Java实现文件分片上传
    • 基于时间缓存优化浏览器轮询阻塞问题
    • 基于EasyExcel实现高效导出
    • 10亿数据高效插入MySQL最佳方案
    • 从开源框架中学习那些实用的位运算技巧
  • CI/CD

    • 基于NETAPP实现内网穿透
    • 基于Gitee实现Jenkins自动化部署SpringBoot项目
    • Jenkins离线安装部署教程简记
    • 基于Nexus搭建Maven私服基础入门
    • 基于内网的Jenkins整合gitlab综合方案简记
  • 监控方法论

    • SpringBoot集成Prometheus与Grafana监控
    • Java监控度量Micrometer全解析
    • 从 micrometer计量器角度快速上手promQL
    • 硬核安利一个监控告警开源项目Nightingale
  • Spring AI

    • Spring AI Alibaba深度实战:一文掌握智能体开发全流程
    • Spring AI Alibaba实战:JVM监控诊断Arthas Agent的工程化构建与最佳实践
  • 大模型评测

    • M2.7 真能打!我用两个真实场景测了测,结果有点意外
    • Qoder JetBrains插件评测:祖传代码重构与接口优化实战
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Java基础

  • 并发编程

    • Java并发编程基础小结
    • 深入理解Java中的final关键字
    • 浅谈Java并发安全发布技术
    • 浅谈Java并发编程中断的哲学
    • 浅谈传统并发编程的优化思路
    • Java线程池知识点小结
    • 浅谈Java线程池中拒绝策略与流控的艺术
    • 浅谈池化技术的优雅关闭
    • 浅谈守护线程与进程优雅关闭
    • 浅谈并发编程等待通知模型
    • synchronized关键字使用指南
    • 深入源码解析synchronized关键字
    • 深入理解synchronized同步机制
    • 详解JUC包下的锁
    • 详解JUC包下各种锁的使用
    • 详解并发编程中的CAS原子类
    • LongAdder源码分析
    • AQS源码解析
    • 深入剖析Java并发编程中的死锁问题
    • 详解Java并发流程控制工具
    • Java并发容器总结
    • 深入解析CopyOnWriteArrayList
    • 详解Java并发编程volatile关键字
    • 聊聊JVM中安全点的概念
    • 并发编程ThreadLocal必知必会
    • CompletableFuture基础实践小结
    • CompletableFuture异步IO密集型任务最佳实践
    • CompletableFuture异步多任务最佳实践
    • CompletableFuture组合流水线任务实践
    • 实现一个简单实用的的并发同步模型
    • 解决Java并发问题的常见思路
    • 来聊一个有趣的限流器RateLimiter
    • 硬核详解FutureTask设计与实现
    • 线程池大小设置的底层逻辑与场景化方案
      • 写在文章开头
      • 线程池调测实践
        • 单计算任务是否可以跑满单个CPU
        • 密集计算任务与CPU调度的关系
        • IO密集型任务调测
        • 结合公式落地IO密集型任务线程池配置
      • 关于线程池公式的更进一步讨论
        • 计算密集型任务的公式的推导
        • 为什么会出现IO密集型线程数为CPU核心数*2+1的错误说法
      • 小结
      • 参考
    • 记一个ConcurrentHashMap使用不当导致的并发事故
    • JS设计模式总结笔记
    • 小程序笔记
    • 《JavaScript教程》笔记
  • JVM相关

  • 深入理解Spring框架

  • Java核心技术
  • 并发编程
sharkchili
2026-03-25
目录

线程池大小设置的底层逻辑与场景化方案

@[toc]

# 写在文章开头

大部分读者可能都会看过网上的几篇文章,对于线程数的设定基本都是采用下面这个公式:

计算密集型=CPU核心数+1
IO密集型=CPU核心数*2+1
1
2

然而事实真的是这样吗?那么为什么tomcat服务器的核心线程数要设置为200呢?基于此问题,笔者也基于个人的经验和实践给出自己的一套方法论,希望对你有帮助。

我是 SharkChili ,Java 开发者,Java Guide 开源项目维护者。欢迎关注我的公众号:写代码的SharkChili,也欢迎您了解我的开源项目 mini-redis:https://github.com/shark-ctrl/mini-redis (opens new window)。

为方便与读者交流,现已创建读者群。关注上方公众号获取我的联系方式,添加时备注加群即可加入。

# 线程池调测实践

# 单计算任务是否可以跑满单个CPU

针对上述的公式,作者认为计算密集型的任务基本都在进行CPU运算,没有所谓的IO等待,所以设置线程池参数时,只需设置为:

CPU核心数+1
1

注意,这里的加1是为了保证及时因为偶发的缺页中断亦或者某些异常导致某个线程消亡,也能利用额外的一个线程跑满CPU时钟周期,以确保在单位时间内尽可能的利用到CPU。

所以我们是否可以得出这个结果,如果我们的计算密集型的任务不断的循环跑,它就能跑满单个CPU呢?

对此我们给出下面这样一段代码,在web程序启动之后直接无限循环:

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class ThreadPoolApplication {

    public static void main(String[] args) {
        SpringApplication.run(ThreadPoolApplication.class, args);

        //空跑一个循环
        while (true) {

        }
    }

}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

将程序部署到服务器上启动,可以看到在笔者16核的服务器上,Cpu6 跑满100%,很明显我们的程序霸占了这个CPU核心,由此可以印证对于CPU在单位时间内只能指向一个线程的指令:

# 密集计算任务与CPU调度的关系

有了上面的理论基础,我们将线程数设置为CPU核心数的一半,看看当前的服务器的运行情况:

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class ThreadPoolApplication {

    public static void main(String[] args) {
        SpringApplication.run(ThreadPoolApplication.class, args);

        //创建CPU核心数一半的线程
        for (int i = 0; i < Runtime.getRuntime().availableProcessors() >> 1; i++) {
            new Thread(() -> {
                //空跑一个循环
                while (true) {

                }
            }).start();
        }

    }

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

和预测的结果一样,对于计算密集型的任务而言,每一个空循环的线程(即每一个线程的指令都会绑定一个CPU核心):

这也就意味着,对于计算型的任务,在满载运行的情况下,可以完全利用单个CPU核心,由此也可推出,对于计算密集型的任务,满载情况下,所有的CPU利用率都会达到100%。

对此我们不妨设置的更极端一点,尝试将线程数设置为CPU核心数的2倍:

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class ThreadPoolApplication {

    public static void main(String[] args) {
        SpringApplication.run(ThreadPoolApplication.class, args);

        //创建CPU核心数2倍的线程
        for (int i = 0; i < Runtime.getRuntime().availableProcessors() << 1; i++) {
            new Thread(() -> {
                //空跑一个循环
                while (true) {

                }
            }).start();
        }

    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

可以看到此时利用率全满了,并且对应的负载也非常显著的提高了,在最近的一分钟可以基本已经达到CPU核心数了:

这里补充一下负载的概念,在单核情况下,负载的值为在0~1之间,这就意味着当前cpu还未满载,用一个比较通俗的比喻,假如单核CPU的负载值为0.5,这就意味着单条车道上有一半的车流经过,还可以容纳一半的车驶入:

业内普遍认为在单核CPU的场景下,负载处于0.7是比较正常标准,如果超过这个值就说明过载了。

同理,笔者的服务器为16核,按照上述所说我们的服务器可以看到有16条车道,所以当负载值小于16即说明有CPU核心未跑满载,一旦负载超过11.2(16*0.7)就意味着我们的系统可能过载了。

我们将执行计算密集型的任务的线程数设置为CPU核心数的2倍,负载不断提升已经超过了11.2,所以对于计算密集型任务,本次线程数的设置是存在问题的:


top - 00:21:38 up  1:09,  1 user,  load average: 17.69, 10.45, 8.23

1
2
3

自此我们就印证了为什么对于计算密集型的任务,我们更简易将线程数设置为趋近于CPU核心数的原因了。

# IO密集型任务调测

为了实现IO密集型实验,笔者基于一台8核心的服务器编写好程序,将计算时间和IO时间尽可能的设置为五五开,如下所示,读者可结合自身服务器性能按需调整:

 public static void main(String[] args) {
        SpringApplication.run(ThreadPoolApplication.class, args);

        ExecutorService threadPool = Executors.newFixedThreadPool(1);
        //单线程的线程池执行一个计算和IO五五开的任务
        threadPool.execute(() -> {
            while (true) {
                //执行循环空跑模拟计算
                for (int i = 0; i < Integer.MAX_VALUE >> 4; i++) {
                    for (int j = 0; j < Integer.MAX_VALUE; j++) {

                    }
                }
                //休眠50ms
                ThreadUtil.sleep(50);
            }

        });

    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

性能监控结果如下,可以看到单核CPU利用率趋近于50%:

top - 17:29:01 up  3:05,  8 users,  load average: 0.66, 0.52, 0.46
Tasks: 499 total,   1 running, 498 sleeping,   0 stopped,   0 zombie
%Cpu0  :  0.0 us,  0.0 sy,  0.0 ni,100.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  :  0.0 us,  0.3 sy,  0.0 ni, 99.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu2  :  0.0 us,  0.3 sy,  0.0 ni, 99.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu3  :  0.0 us,  0.0 sy,  0.0 ni,100.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu4  :  0.0 us,  0.0 sy,  0.0 ni,100.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu5  :  0.0 us,  0.0 sy,  0.0 ni,100.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu6  :  0.0 us,  0.0 sy,  0.0 ni,100.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
# 趋近于50%
%Cpu7  : 43.0 us,  0.0 sy,  0.0 ni, 57.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
1
2
3
4
5
6
7
8
9
10
11

基于这个比例,我们将线程数设置为CPU核心数再次运行,最终运行结果如下,可以看到所有的CPU利用率都趋近于50%:

Tasks: 494 total,   1 running, 493 sleeping,   0 stopped,   0 zombie
%Cpu0  : 53.2 us,  0.0 sy,  0.0 ni, 46.8 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  : 53.7 us,  0.0 sy,  0.0 ni, 46.3 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu2  : 49.3 us,  0.0 sy,  0.0 ni, 50.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu3  : 52.7 us,  0.0 sy,  0.0 ni, 47.3 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu4  : 52.7 us,  0.0 sy,  0.0 ni, 47.3 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu5  : 53.3 us,  0.3 sy,  0.0 ni, 46.3 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu6  : 53.2 us,  0.0 sy,  0.0 ni, 46.8 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu7  : 53.0 us,  0.0 sy,  0.0 ni, 47.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  7995716 total,  5055564 free,  1529664 used,  1410488 buff/cache
KiB Swap:  2097148 total,  2097148 free,        0 used.  6155340 avail Mem

1
2
3
4
5
6
7
8
9
10
11
12

# 结合公式落地IO密集型任务线程池配置

根据《Java并发编程实战》所说,对于IO密集型任务,线程数可按照如下公式获取

nThread=nCPU * uCPU * (1+w/c)
1

对应的参数含义是:

nThread:表示程序中应该使用的线程数量。 nCPU:表示系统中可用的CPU核心数量。 uCPU:表示每个CPU核心的利用率(通常是一个介于0到1之间的值)。 w/c:表示程序中等待时间(wait time)与计算时间(compute time)的比率。

因为我们的CPU为8核,我们希望全部利用,假设每个利用率为90%,按照我们IO时间和计算时间五五开来算,线程数的计算公式为:

nThread=nCPU * uCPU * (1+w/c)
 	   = 8 * 0.9 * (1+1)
 	   = 14.4
 	   ≈ 15
1
2
3
4

因此我们将线程数设置为15个再次启动并运行,可以看到最终的CPU利用率和预期的基本一致,我们可能还需要结合服务器的实际使用情况进行上下浮动调整:

top - 19:08:52 up  3:50,  8 users,  load average: 1.16, 2.44, 2.65
Tasks: 499 total,   3 running, 496 sleeping,   0 stopped,   0 zombie
%Cpu0  : 89.7 us,  0.0 sy,  0.0 ni, 10.3 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  : 91.4 us,  0.3 sy,  0.0 ni,  8.3 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu2  : 87.0 us,  0.0 sy,  0.0 ni, 13.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu3  : 91.7 us,  0.0 sy,  0.0 ni,  8.3 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu4  : 87.7 us,  0.0 sy,  0.0 ni, 12.3 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu5  : 91.0 us,  0.0 sy,  0.0 ni,  9.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu6  : 93.7 us,  0.0 sy,  0.0 ni,  6.3 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu7  : 86.7 us,  0.0 sy,  0.0 ni, 13.3 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  7995716 total,  5025368 free,  1558680 used,  1411668 buff/cache
KiB Swap:  2097148 total,  2097148 free,        0 used.  6125500 avail Mem
1
2
3
4
5
6
7
8
9
10
11
12

# 关于线程池公式的更进一步讨论

# 计算密集型任务的公式的推导

关于计算密集型任务推导公式,很多读者都是死记硬背,这里读者从这里从工作机制和公式推导两个角度进行补充说明。 计算密集型任务即任务不涉及任何IO操作导致阻塞而让出CPU时间片,这意味着所有的任务都必须通过CPU完成运算才算结束,举个例子:假设我们的服务器只有一个CPU,单个任务运算耗时为200ms,如果有1000个任务需要执行,无论如何这份任务在CPU上的执行总时间都是200000ms也就是200s:

操作系统在这其中唯一能做的,也就是为了避免任务饥饿在某个任务执行100ms时将其切换,执行另外一个任务,但是两个任务总的耗时永远是400ms,且必须在CPU上执行完成,所以即使设置再多的线程也没有任何意义:

从公式的角度来说,对应计算密集型的任务w也就是程序因为IO的等待时长为0,按照极限思维来算,对应的推导过程如下

nThread= nCPU * uCPU * (1+w/c)
 	   = nCPU * uCPU * (1+0/c)
 	   = nCPU * uCPU
1
2
3

最终推导出的线程数也就是基本等同于CPU核心数*CPU利用率也就是CPU核心数,考虑到一些计算异常亦或者缺页中断等原因导致线程消亡,我们一般会按照经验法则多1个线程,这就是为什么计算密集型的公式为CPU核心数+1,同时也因为计算密集型的任务一般不会有太大的耗时,所以大部分情况下对于此类任务都没有基于CPU利用率去限制线程数。

# 为什么会出现IO密集型线程数为CPU核心数*2+1的错误说法

这个公式是很多八股文中经常会提及到的一点,按照笔者的推测,估计是某些博主没有真正的理解线程数推导公式的含义就盲目按照理想情况下所得出的,本质上nThread= nCPU * uCPU * (1+w/c)这个公式的含义是:

利用任务IO阻塞的等待时长和计算时长的占比,推导出某些任务因为任务阻塞而挂起时可以顺便执行多少计算任务

假设我们的任务查询数据库也就是IO耗时为100ms,计算时长也是100ms,按照公式计算为:

nThread= nCPU * uCPU * (1+w/c)
       = nCPU * uCPU * (1+100ms/100ms)
       = nCPU * uCPU * 2
1
2
3

可以看到最终的结果就是nCPU * uCPU * 2,是不是觉得很熟悉?没有错,大部分错误的八股文把IO密集型任务都按照计算耗时与IO阻塞耗时五五开进行推导得出nCPU * uCPU * 2,然后也学着计算密集型的套路:

  1. 去掉CPU利用率
  2. 避免缺页中断等异常线程数+1

最终得出2* CPU +1,所以这也正是为什么笔者一直强调对于一些业界正确的实践要参考一些权威性的书籍资料去了解掌握。

反驳了CPU核心数*2+1这个公式之后,我们再来说说正确公式的由来,在上文中笔者提到该公式本质上就是利用任务IO等待耗时和计算耗时的占比,来推算IO阻塞期间可以提前执行掉多少的运算任务,假设我们现在有这样一个场景:

  1. 服务器CPU核心数为18
  2. 计算耗时为1ms
  3. IO耗时为200ms

实际上(1+w/c)这个过程本质上就是在计算针对这个任务,在IO阻塞期间可以提前完成多少计算操作,按照我们的计算比来说每个任务都有200ms的IO阻塞,这也就意味着在200ms的阻塞期间,理想情况下(如果CPU全心全意只执行我们这个程序的任务),阻塞期间可以处理w/c也就是200个计算操作:

基于上述单核的推导过程,我们再补充CPU核心数和利用率才有了下面的公式和计算过程:

nThread= nCPU * uCPU * (1+w/c)
       = 18 * 1 * (1+200ms/1ms)
       ≈3600
1
2
3

按照当前任务的说明,我们推算:

  1. 理想情况下单核CPU单位时间内可以处理1000个计算操作,换算成我们的18核服务器,也就是每秒可以处理大约18*1000也就是18000个任务。
  2. 基于我们推测的3600个线程数,按照每个任务200ms的IO来算,每个线程1s内可以处理大约1000/200也就是5个任务,那么3600个线程大约也是可以达到18000的任务

为了印证第一点的预期值和我评估的线程数值一致,我们写下下面这样一段代码,查看当前线程数的设定在单位时间内是否可以处理18000个任务:

//qps 计数器
    private static final AtomicInteger count = new AtomicInteger(0);

    //按照 1ms cpu处理耗时,推算合理运算线程池数
    private static final ExecutorService threadPool = Executors.newFixedThreadPool(3600);

    public static void main(String[] args) {

        int taskCount = 500_0000;

        //计算每秒处理的任务数
        new Thread(() -> {
            while (true) {
                Console.log("qps:{} ", count.get());
                count.getAndSet(0);
                ThreadUtil.sleep(1000);
            }
        }).start();


        for (int i = 0; i < taskCount; i++) {
            threadPool.execute(() -> {
                ThreadUtil.sleep(200);
                count.incrementAndGet();
            });
        }
        

    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

最终输出结果如下,可以看到,在程序启动后JIT预热阶段完成后,基于我们设定的线程数是可以完成单位时间内执行18000个任务:

同理,我们再补充一个案例来推导这个公式的实效性以避免欠拟合:

  1. 服务器18核
  2. 计算耗时2ms
  3. IO耗时 200ms

同理按照公式推导大约需要1800个线程,结合验证:

  1. 单核CPU单位时间内可以处理1000/2也就是500个任务,也就是最终结果应该是9000个任务
  2. 我们推算出的1800个线程,单位时间内每个线程可以处于1000/202≈5,1800*5也可以达到9000

基于笔者的机器性能,笔者也出这样一段耗时2ms的代码:

public static int sum() {
        long begin = System.currentTimeMillis();
        long sum = 0;
        for (int i = 0; i < 800_0000; i++) {
            sum += i;
        }
        long end = System.currentTimeMillis();
        if (sum != 1) {

            return (int) (end - begin);
        }
        return 0;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13

同理压测代码如下:

 //qps 计数器
    private static final AtomicInteger count = new AtomicInteger(0);

    //按照 1ms cpu处理耗时,推算合理运算线程池数
    private static final ExecutorService threadPool = Executors.newFixedThreadPool(1800);

    public static void main(String[] args) {

        int taskCount = 500_0000;

        //计算每秒处理的任务数
        new Thread(() -> {
            while (true) {
                Console.log("qps:{} ", count.get());
                count.getAndSet(0);
                ThreadUtil.sleep(1000);
            }
        }).start();


        for (int i = 0; i < taskCount; i++) {
            threadPool.execute(() -> {
                sum();
                ThreadUtil.sleep(200);
                count.incrementAndGet();
            });
        }


    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

最终输出结果如下,可以看到理想情况下,基于该公式是可以得到预期的结果:

# 小结

上述的线程池设置更多是基于理想情况下的调整设置,读者在进行压测调整时,还需要结合机器实际使用情况进行适当增减,所以总的来说线程池参数的设定需要符合以下几个原则:

  1. 计算密集型任务应该为CPU核心数上下浮动。
  2. IO密集型应该通过公式2得到一个预估的值并结合生产环境的情况不断测试得到一个理想的数值。
  3. 大部分场景下我们的系统并没有太大的压力,不需要那么合适的线程数,对于这种简单的异步场景,我们只需设置为CPU核心数即可。

我是 sharkchili ,CSDN Java 领域博客专家,开源项目—JavaGuide contributor,我想写一些有意思的东西,希望对你有帮助,如果你想实时收到我写的硬核的文章也欢迎你关注我的公众号: 写代码的SharkChili 。 因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。

# 参考

别再纠结线程池池大小、线程数量了,哪有什么固定公式 | 京东云技术团队:https://zhuanlan.zhihu.com/p/657320656?utm_medium=social&utm_oi=738079643164221440&utm_psn=1734183676982435840&utm_source=wechat_session (opens new window)

Understanding Linux CPU Load - when should you be worried?:https://scoutapm.com/blog/understanding-load-averages (opens new window)

编辑 (opens new window)
上次更新: 2026/03/26, 01:05:31
硬核详解FutureTask设计与实现
记一个ConcurrentHashMap使用不当导致的并发事故

← 硬核详解FutureTask设计与实现 记一个ConcurrentHashMap使用不当导致的并发事故→

最近更新
01
基于EasyExcel实现高效导出
03-25
02
从开源框架中学习那些实用的位运算技巧
03-25
03
浅谈分布式架构设计思想和常见优化手段
03-25
更多文章>
Theme by Vdoing | Copyright © 2025-2026 Evan Xu | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×
×