禅与计算机 禅与计算机
首页
  • 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并发编程基础小结
    • 深入理解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技术原理
    • 探索JVM的隐秘角落:元空间详解
  • 深入理解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技术科普短视频制作全攻略
  • 元认知

    • 摩擦感:AI时代的写作自省
    • 从断墨寻径浅谈程序员的元学习能力
    • 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

    • 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插件评测:祖传代码重构与接口优化实战
  • AI工具链

    • Claude Code 实战指南:从安装配置到企业级开发流程
    • 一次 Claude Code 启动失败的 AI 辅助排查复盘
    • Claude Code 记忆管理:CLAUDE.md 最佳实践
关于
收藏
  • 分类
  • 标签
  • 归档
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并发编程基础小结
    • 深入理解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技术原理
    • 探索JVM的隐秘角落:元空间详解
  • 深入理解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技术科普短视频制作全攻略
  • 元认知

    • 摩擦感:AI时代的写作自省
    • 从断墨寻径浅谈程序员的元学习能力
    • 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

    • 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插件评测:祖传代码重构与接口优化实战
  • AI工具链

    • Claude Code 实战指南:从安装配置到企业级开发流程
    • 一次 Claude Code 启动失败的 AI 辅助排查复盘
    • Claude Code 记忆管理:CLAUDE.md 最佳实践
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Java基础

  • 并发编程

    • Java并发编程基础小结
    • 深入理解Java中的final关键字
    • 浅谈Java并发安全发布技术
    • 浅谈Java并发编程中断的哲学
    • 浅谈传统并发编程的优化思路
      • 引言
      • 关于性能优化的一些基本概念
        • 性能与可伸缩性的关联
        • 可伸缩性
        • 性能指标评估标准
      • Amadhl定律
        • 什么是Amadhl
        • Amadhl定律对于并发优化的哲学
      • 多线程的瓶颈
        • 上下文切换
        • 内存同步语义的危害
        • 同步阻塞
      • 减少锁竞争
        • 关于锁的一些优化论
        • 缩小锁的范围
        • 降低锁的请求频率
        • 锁分段的哲学
        • 避免热点域
        • 读写分离的思想
        • 原子操作
      • 并发程序监控调优排查思路
        • 同步性能的排查思路
        • 尽可能不用对象池
      • 小结
      • 参考
    • 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设计与实现
    • 线程池大小设置的底层逻辑与场景化方案
    • 记一个ConcurrentHashMap使用不当导致的并发事故
    • ArrayBlockingQueue源码分析
    • PriorityQueue源码分
    • DelayQueue源码解析
  • JVM相关

  • 深入理解Spring框架

  • Java核心技术
  • 并发编程
sharkchili
2025-11-17
目录

浅谈传统并发编程的优化思路

# 引言

通过并发来散列串行任务提升了持续整体吞吐量,而本文将结合《java并发编程实战》一书中的一些传统理论来探讨并发编程中性能的一些优化的一些基本理念和常见手段,希望对你有所启发。

你好,我是 SharkChili ,禅与计算机程序设计艺术布道者,希望我的理念对您有所启发。

📝 我的公众号:写代码的SharkChili
在这里,我会分享技术干货、编程思考与开源项目实践。

🚀 我的开源项目:mini-redis
一个用于教学理解的 Redis 精简实现,欢迎 Star & Contribute:
https://github.com/shark-ctrl/mini-redis (opens new window)

👥 欢迎加入读者群
关注公众号,回复 【加群】 即可获取联系方式,期待与你交流技术、共同成长!

# 关于性能优化的一些基本概念

# 性能与可伸缩性的关联

对于性能的提升我们更多是需要去抓住问题的本质,而优化的根本目的就是在有限的资源限定下,用尽可能少的资源做尽可能多的事情,而这里的资源通常的指代下面这些计算机资源:

  1. CPU时钟周期
  2. 网络带宽
  3. IO带宽
  4. 数据库资源
  5. 计算机内存
  6. 磁盘空间
  7. ......

当程序吞吐量受到这些资源的限制而进入瓶颈时,我们通常称这些任务是资源密集型阻塞,例如:

  1. 某个线程长时间得不到CPU执行就是CPU密集型
  2. 文件读取操作因为IO wait时间过长导致阻塞也就是IO密集型

由于现在java开发大部分都是面向restful接口编程,对于任务的界定也不能完全按照上述的说法一概而论,需要从系统监控甚至是java监控诊断面板进行综合分析,例如笔者下面这段代码示例,这里采用一个简单的线程池模拟并发的接口请求,我们无法明确推荐这么细粒度的接口实现细节,所以我们就需要借住一些监控诊断工具进行诊断:

public static void main(String[] args) throws InterruptedException {
        ExecutorService threadPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() << 1);
        CountDownLatch latch = new CountDownLatch(1_0000);
        for (int i = 0; i < 1_0000; i++) {
            threadPool.execute(() -> {
                //执行异步任务
                doExecuteTask();
                latch.countDown();

            });


        }
        latch.await();
        threadPool.shutdown();

    }

    private static void doExecuteTask() {
        //执行io任务
        ioTask();
        //执行计算任务
        calculateTask();
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

以笔者为例,这里直接通过artahs attach到目标进行,通过trace指令追踪异步任务doExecuteTask的方法栈,可以看到大部分时间都处于io任务执行,所以我们可以很明显的推断出此类任务属于IO密集型的接口:

所以在进行性能排查的时候,我们更多要做就是通过观察服务器上各项指标明确任务的类型才能进行更近一步的优化。不能意味的当性能下降的时候就去盲目的增加线程数。因为引入多线程的同时会引入如下开销:

  1. 上下文切换开销
  2. 线程创建与销毁
  3. 资源同步协调
  4. 线程调度

按照并发编程性能的哲学,要做到性能的优化要求我们尽快的利用到现有的资源,让其时刻保持忙碌(例如CPU时钟周期做有意义的事情,而不是忙于上下文切换)

例如我们有这样一批并发任务,计算时间占用50ms,而io耗时为150毫秒,按照优化方法论,我们应该设定合适的线程数保证每个线程完成运算后进入IO状态就立刻挂起切换下一个线程执行CPU运算任务,保证尽可能的平衡稳定的运算任务并榨取CPU性能。

java-concurrency-optimization.drawio

# 可伸缩性

可伸缩性的概念,即增加计算机资源后,应用程序执行能力亦或者说吞吐量会相应的提高,而这些资源通常指代:

  1. 计算机内存
  2. 网络带宽
  3. 磁盘IO带宽(例如硬盘和SSD磁盘)
  4. CPU

在性能优化涉及多快和多少我们必须要理解,这是两个概念,有时候这两者是矛盾的,即为了提升资源利用率,我们可能会将一个任务分为无数个子任务在多核CPU中处理,但是性能却不一定提升,例如下面这段简单的运算代码单线程只需要跑2ms,对应多线程并发争抢CPU时间片之后,耗时变为88ms:

long begin = System.currentTimeMillis();
        int sum = 0;
        for (int i = 0; i < 100_0000; i++) {
            sum ++;
        }
        long end = System.currentTimeMillis();
        Console.log("sum:{} 耗时:{}", sum, end - begin);//2ms


        AtomicInteger count = new AtomicInteger(0);
        begin = System.currentTimeMillis();
        IntStream.range(0, 100_0000).parallel().forEach(i -> count.incrementAndGet());
        end = System.currentTimeMillis();
        Console.log("count:{} 耗时:{}", count.get(), end - begin);//88ms
1
2
3
4
5
6
7
8
9
10
11
12
13
14

亦或者我们通过程序上的优化提高单线程性能的性能表现,即该线程已经尽可能的利用所有限定以内的资源做到尽可能的提高,而这往往会破坏可伸缩性,即资源增加了性能也不会有多少提升。例如我们希望在应用中实现一个懒加载且线程安全的单例模式,一般人可能会想到利用synchronized实现一个DCL双重锁校验的单例模式:

public class SingleObj {
    private volatile static Object object;
    
    public static Object getInstance() {
        if (object == null) {
            synchronized (SingleObj.class) {
                if (object == null) {
                    object = new Object();
                }
            }
        }
        return object;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

实际上,在JVM类加载的特性下,我们完全可以利用static和final关键字保证安全发布的同时,实现只有在线程进行类加载的时候再去创建这个静态对象,同时利用final关键字保证不可变性从而做到线程安全:

public class SafeSingleObj {
    private static final Object object;

    static {
        object = new Object();
    }


    public static Object getInstance() {
        return object;
    }
}

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

对应的笔者这里也给出压测代码,感兴趣的读者可以试一下,后者仅仅是利用单线程技术,尽可能的让所有线程尽可能跑满CPU进一步的提升了单例模式的表现,这就是典型单线程层面的优化,破坏了可伸缩性的典型:

long begin = System.currentTimeMillis();
        IntStream.range(0, 100_0000).parallel().forEach(i -> {
            Object instance =SingleObj.getInstance();
            if (instance == null) {
                throw new RuntimeException("单例对象为空");
            }
        });
        long end = System.currentTimeMillis();
        Console.log("耗时:{}ms", end - begin);


        begin = System.currentTimeMillis();
        IntStream.range(0, 100_0000).parallel().forEach(i -> {
            Object instance = SafeSingleObj.getInstance();
            if (instance == null) {
                throw new RuntimeException("单例对象为空");
            }
        });
        end = System.currentTimeMillis();
        Console.log("耗时:{}ms", end - begin);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

同理按照现代的分布式架构,一个原本单体的应用程序因为分层或者某些原因分布到不同的机器上,虽然资源利用率提升,但是性能(例如分布式事务问题)却可能会下降,因为它们之间的调度涉及了网络通信以及同步等开销。

# 性能指标评估标准

性能优化的决策大部分是需要理性的分析思考,要以基准测试为主,不要盲目的去评估,必须有个绝对的标准才能进行评估,例如在小规模的数据排序下,冒泡就是优于快速排序。整体来说,针对性能评估的标准应该是:

  1. 最差时间
  2. 平均耗时
  3. 可预知性(处理时间范围是否波动可控)

总的来说性能优化必须是要有具体标准和场景进行分析才能决策,即程序不要过早的优化,而是先正确的跑起来,然后在必要时再去提升它的速度。毕竟性能优化这件事还有额外的维护负担和测试回归成本,通过优化架构或者设计都会使得程序的复杂度增加,使得整体变得晦涩难懂。

# Amadhl定律

# 什么是Amadhl

有些问题确实是增加更多的资源,执行的速度理论上也就会越快,这也就是著名的Amadhl定律,其理论的对应公式如下,对应语义为:

  1. F是程序的串行执行部分
  2. N是处理器个数

对应的公式如下:

image-20250808004057139

基于这个公式,我们假设如下3个场景来印证串行时间对于加速比的限制:

先来说说第一个场景,我们的串行占用50%,假设我们的处理器资源无限大,使得右边的分数几乎趋近于0,这也就意味着一个方法串行时间与并行时间五五开的情况下,加速比最多只有2也就是最多提升2倍:

可能听着有点抽象,笔者举个具象化的情况,我们现在有一个CPU,对应2个线程,这两个线程并行时间和串行时间都500ms也就是上文所说的五五开,试想当前的计算过程:

  1. 线程0先拿到cpu完成串行和并行(因为当前只有一个cpu,做不到并行)
  2. 其他线程按照顺序去完成各自的任务
  3. 线程1同理串行执行
  4. 2个线程串行执行500ms,加上并行共用500ms也就是1500ms

此时我们在增加一个处理器,变为两个处理器,对应的计算过程为:

  1. 线程0和线程1同时执行并行部分,总共耗时500ms
  2. 线程0先利用处理器0执行串行部分耗时500ms
  3. 线程1利用处理器1执行串行部分,耗时500ms
  4. 两个线程串行任务各占用一个处理器完成500ms的任务,加上并行耗时500ms,最终变为1000ms
  5. 加速比为1500/1000大约是1.5,无线趋近于2

了解阿姆达尔定律的理念之后,我们再来一个场景,假设处理器为10,串行占用时间为10%,对应的加速比差不多是5%左右,对应的计算公式为:

加速比 <= 1/(0.1+(1-0.1)/10)
			<= 1/0.19
			<= 5.3
1
2
3

提升至100个处理器最高使用率也只能达到9%的使用率左右,试想这样一个场景,在所有硬件资源条件不变的情况下,串行耗时代码段优化为5%,对应的加速比则是无线趋近于10:

加速比 <= 1/(0.05+(1-0.05)/10)
			<= 10
1
2

由此我们也可以知晓,性能瓶颈除了计算资源以外,还有串行时间的耗时,如果能够尽可能得去缩小串行部分耗时,程序的性能表现也会有所提升,就例如下面这段队列获取任务执行的代码段,它的串行时间的瓶颈,就在于多线程并发的访问容器的同步阻塞的开销,此类问题的优化工作,我们也需要更多的去这种同步、加锁等方式的阻塞代码段:

public class WorkThread implements Runnable {

    private final BlockingQueue<Runnable> queue;

    public WorkThread(BlockingQueue<Runnable> queue) {
        this.queue = queue; 
    }

   

    @SneakyThrows
    @Override
    public void run() {
        while (true) {
            //多线程并发访问阻塞队列,这块就是串行部分的性能瓶颈
            Runnable task = queue.take();
            task.run();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# Amadhl定律对于并发优化的哲学

基于Amadhl定律的思想,本质上优化并发算法时,在资源限定的情况下,我们更多去去强调降低串行时间,说的更具体一点就是降低java内置锁的粒度:

第一种方式为锁分解,将一个锁分为两个锁,针对职责工作进行分离,针对不同的锁采用不同的策略,例如读写锁,针对读请求不修改的场景,这种方式能够最大化多线程读取临界资源的效率:

java-concurrency-optimization-2.drawio

另一种则是锁分段,将一个锁分解为多个锁段,将多线程相同操作的竞争锁的场景,让线程按照指定算法去获取不同的锁,分散竞争的压力:

java-concurrency-optimization-3.drawio

这也是目前并发编程锁优化最常见的几种优化策略,读者可以了解一下这一块的思想,笔者会在后文中给出详尽的解析。

# 多线程的瓶颈

# 上下文切换

先来说说线程上下文切换,从操作系统来说线程调度切换时,CPU是需要保存线程上文信息将其存入寄存器然后才能挂起的,这使得线程上下文切换就存在如下几个开销点:

  1. 频繁上下文切换,CPU时钟周期都用于切换,而线程运算任务
  2. 频繁切换导致CPU缓存失效导致局部性原理的优势丢失,导致执行效率降低

以Unix系统为例,一般来说线程上下文的开通过vmstat指令进行查看,如果报告线程上下文切换开销大于10%就表示调度活动频繁了。

以笔者的系统为例,可以看到cs为1,上下文切换开销就不算大:

procs -----------memory---------- ---swap-- -----io---- -system-- -------cpu-------
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st gu
 0  0      0 6914216  19216 425612    0    0  3371   404  224    1  1  1 98  0  0  0
1
2
3

# 内存同步语义的危害

为了保证并发可见性,JVM要求指令指令不会重排序,它会在JIT编译器甚至CPU处理器上插入内存屏障解决问题,这使得很多JIT优化都失效。尽管JVM也针对不合理的并发采用了一些手段进行辅助优化,也就是逃逸分析等,但我们建议不涉及同步的代码逻辑尽可能的去避免使用一些并发关键字或者并发工具。

# 同步阻塞

针对同步块阻塞我们可以结合场景选用自旋锁或者阻塞挂起线程锁,对应的应用场景要求也很简单:

  1. 如果上锁处理很快且等待时间很短,用自旋等待锁原地自选一会,常见的工具也就是juc包下的原子类,例如AtomicInteger
  2. 如果并发竞争激烈,自旋锁导致CPU时间片因为各种非必要的自旋等待运算而浪费,这种情况下,我们也更推荐直接使用java内置监视锁,也就是synchronized关键字

当然这以上都是一些比较感性的说法,按照jdk8的优化,synchronized关键字已经有了锁升级的概念,它会根据当前线程的竞争激烈程度依次自动的完成如下的锁升级:

  1. 无锁
  2. 偏向锁
  3. 轻量级锁也就是自旋锁
  4. 重量级锁也就是涉及内核态调用的操作系统级别的mutex锁

关于逃逸分析和synchronized锁升级的概念,感兴趣的读者可以移步笔者这两篇文章:

逃逸分析在Java中的应用与优化:https://mp.weixin.qq.com/s/0flB6Hlqox5jrVoR6JQkkA (opens new window)

深入解析Java中的synchronized 关键字:https://mp.weixin.qq.com/s/rMvpMSrfUsLeQ_WGObeQ4w (opens new window)

# 减少锁竞争

# 关于锁的一些优化论

在并发编程中,锁的同步是耗时的重要优化点,对于可伸缩性而言最主要的威胁本质上就是独占锁的资源锁,而判断并发竞争激烈的程度的条件有:

  1. 独占锁的请求频率
  2. 每次持有锁的时间

如果二者的乘积很小,那么这个程序的并发竞争就相对激烈,反之亦然。按照并发哲学来说,针对锁的优化有三种方式:

  1. 减小持有锁的时间(减小锁的范围)
  2. 降低锁的请求频率(减小锁的管控粒度)
  3. 采用带有协调性质的独占锁,提升程序的并发度

# 缩小锁的范围

这里我们先来介绍一个比较基础的优化点,在并发程序中的优化,锁的范围也是影响程序性能的表现的重要指标,假设同步代码块耗时为2ms并且所有的操作都需要这把锁,那么无论有多少个处理器,吞吐量也不会超过500个操作每秒,所以我们可以适当缩小锁的范围降低同步代码块的耗时,以提升程序的并发变现。

例如下面这个例子,为了操作共享资源的map,锁住了整个方法,这其中还包含一段休眠的IO逻辑:

 private static HashMap<String, String> map = new HashMap<>();


    public static synchronized String getVal(String key) {
        ThreadUtil.sleep(1000);
        return map.get(key);
    }
1
2
3
4
5
6
7

对此我们也给出一个10个并发的压测逻辑,最终的耗时差不多在10s左右:

int size = 10;
        ExecutorService threadPool = Executors.newFixedThreadPool(size);
        CountDownLatch countDownLatch = new CountDownLatch(size);
        long begin = System.currentTimeMillis();
        for (int i = 0; i < size; i++) {
            threadPool.execute(() -> {
                getVal("1");
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();

        long end = System.currentTimeMillis();
        Console.log("总耗时:{}ms", end - begin);
1
2
3
4
5
6
7
8
9
10
11
12
13
14

所以我们可以通过缩小synchronized 的范围缩小同步范围来减少持有锁时需要处理的指令数量以降低同步代码块的耗时,让CPU单位时间内可以处理尽可能多的线程以提升程序的并发度。

于是我们将代码改成下面这样,将非必要保证线程安全的操作放在锁之外,经过压测耗时基本稳定在1s左右,提升非常明显:

private static HashMap<String, String> map = new HashMap<>();


    public static String getVal(String key) {
        ThreadUtil.sleep(1000);
        synchronized (Main.class) {
            return map.get(key);
        }
    }
1
2
3
4
5
6
7
8
9

当然这样的做法我们还是需要考虑到原子性,例如并发操作的更新,此时可能缩小锁的范围就可能存在一致性问题。

# 降低锁的请求频率

另一种减小锁持有时间的方式是,缩小锁的请求频率,如果一个锁需要保护多个独立状态的同步,这就导致许多非必要的同步阻塞,就像下面这段代码,获取user和获取shop的并发操作都采用了同一个对象实例锁,这就可能导致获取user期间shop的操做被阻塞,程序的吞吐量下降:

public synchronized void getUser() {
       //......
}
    
public synchronized void getShop() {
        //......
}
1
2
3
4
5
6
7

所以解决方案就是将锁分解,将不同资源交由不同的锁管控,将锁的请求频率分解到不同的锁进行管控,从而提升程序的并发性能表现:

public synchronized void getUser() {
        synchronized (userLock){     
        System.out.println("getUser");
        }
    }

    public  void getShop() {
            synchronized (shopLock) {
                System.out.println("getShop");
 					}
 }
1
2
3
4
5
6
7
8
9
10
11

# 锁分段的哲学

把竞争激烈的锁分解为两个锁,这两个锁还是存在激烈的竞争,虽然采用两个线程提升的一部分的可伸缩性,但是在多核处理器中,仍然无法做到资源的最大化利用,所以我们也可以参考ConcurrentHashMap的思想,将锁分解计数拓展为通过一组独立对象进行统一管控,这也就是著名的锁分段技术,通过4个锁的数组,在散列算法和数据分布均匀的情况下,竞争的激烈程度将会压缩至原来的1/4:

public class SyncMap<K, V> {
    private final static int buckets = 1 << 2;
    private final static int mask = buckets - 1;
    private final Node<K, V>[] table = new Node[buckets];
    private final Object[] locks = new Object[buckets];
    private int size = 0;


    public SyncMap() {
        //初始化分段锁
        for (int i = 0; i < locks.length; i++) {
            locks[i] = new Object();
        }
    }

    /**
     * 链表的node节点
     */
    private class Node<K, V> {
        private K key;
        private V value;
        private Node next;

        public Node(K key, V value) {
            this.key = key;
            this.value = value;
        }
    }

    public V get(Object key) {
        //取模运算获得索引的位置
        int idx = key.hashCode() & mask;
        //上分段锁
        synchronized (locks[idx]) {
            Console.log("线程获取分段锁:{}", idx);
            for (Node<K, V> node = table[idx]; node != null; node = node.next) {
                if (node.key.equals(key)) {
                    return node.value;
                }
            }
            return null;
        }

    }

    public void put(K key, V value) {
        int idx = key.hashCode() & mask;
        //上锁读取数据
        synchronized (locks[idx]) {
            Node<K, V> node = table[idx];
            //若为空则直接初始化节点
            if (node == null) {
                table[idx] = new Node<>(key, value);
                size++;
            } else {
                Node pre = null;
                while (node != null && !node.key.equals(key)) {
                    pre = node;
                    node = node.next;
                }

                if (node == null) {
                    pre.next = new Node(key, value);
                    size++;
                } else {
                    node.value = value;
                }
            }
        }
    }

    public int size() {
        return size;
    }

   
}
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77

很多读者反馈对于并发编程的程序测试无从下手,基于此问题,笔者索性将ai关闭以沉浸式的方式完成编码并给出测试单元的用例,总体来说并发编程测试的方式主要是抓住多线程操作后一些可以验证状态的的字段在程序结束后进行校验,例如我们现在想验证相同的key是否会完成覆盖,那么对应在我们的程序中的结果就是size不变值被覆盖

 SyncMap<Integer, String> syncMap = new SyncMap<>();
        //塞到底层的bucket0
        Thread t0 = new Thread(() -> {
            syncMap.put(0, "0");
        });
        //塞到底层的bucket0模拟冲突
        Thread t1 = new Thread(() -> {
            syncMap.put(0, "new");
        });

        t0.start();
        t1.start();
        t0.join();
        t1.join();
        //基于最终状态进行校验
        Assert.equals(1, syncMap.size());
        Assert.equals("new", syncMap.get(0));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

然后就是测试多个bucket下是否可以完成拉链法,例如key为0和4都会落到bucket-0上,那么我们的最终校验就是判断这两个键值对是否都可以查出来,然后查看bucket-0这个桶下的链表size是否为2即可,这里笔者就简单的做个值查询,关于bucket桶的校验在断点上做个确认:

 SyncMap<Integer, String> syncMap = new SyncMap<>();
        //塞到底层的bucket0
        Thread t0 = new Thread(() -> {
            syncMap.put(0, "0");
        });
        //塞到底层的bucket0模拟冲突
        Thread t1 = new Thread(() -> {
            syncMap.put(4, "4");
        });

        t0.start();
        t1.start();
        t0.join();
        t1.join();
        //基于最终状态进行校验
        Assert.equals("4", syncMap.get(4));
        Assert.equals("0", syncMap.get(0));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

针对其他类型的测试也是差不多意思,读者可以根据自己的用例场景自行覆盖。

# 避免热点域

另一个方面,我们也可以使用缓存技术将热点数据进行缓存,当然这些热点域可能也会限制可伸缩性。例如HashMap进行修改操作时会使用维护一份size变量,保证容量和判空操作的时间复杂度压降为O(1),但是在并发操作下,因为需要同步的原因,同步的等待耗时又会导致可伸缩问题(即增加硬件资源之后,程序的吞吐量不减反增):

java-concurrency-optimization-4.drawio

对此ConcurrentHashMap则是采用分段进行技术枚举遍历不同分段中的容器数进行累加,来提升并发计数维护的效率,如下图,从宏观角度来说,ConcurrentHashMap进行size累加时默认会先通过CAS的方式进行累加,一旦失败就回尝试累加到counterCells中,同时考虑到键值对的增加counterCells竞争也会变变得激烈,该数组也会根据激烈情况进行动态扩容,这种理念也存在于jkd8的计数工具LongAddr。

然后在进行size计算时就会讲baseCount和counterCells数组遍历累加,得到size的值:

java-concurrency-optimization-5.drawio

基于上述的概念,我们也给出ConcurrentHashMap的size的源码实现,如下所示,本质上就是将数组中所有的元素和baseCount进行累加:


public int size() {
        long n = sumCount();
        return ((n < 0L) ? 0 :
                (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
                (int)n);
    }

final long sumCount() {
        CounterCell[] as = counterCells; CounterCell a;
  		//拿到baseCOunt
        long sum = baseCount;
  		//遍历数组元素累加到sum
        if (as != null) {
            for (int i = 0; i < as.length; ++i) {
                if ((a = as[i]) != null)
                    sum += a.value;
            }
        }
  //返回size的值
        return sum;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 读写分离的思想

将读写按照不同维度去优化,提升读多写少场景下的读并发操作可同步执行,写操作进行适当互斥提升程序的并发性能表现。对于读写锁,感兴趣的读者可以阅读一下笔者这篇文章:

java并发编程控制工具小结:https://mp.weixin.qq.com/s/jk6tobOgvQAD87ugc4zoRw (opens new window)

# 原子操作

对于简单的并发修改操作,我们可以采用原子类这些基于底层硬件原子交换指令直接完成这种细粒度的原子交换修改,降低并发操作的时间来提升可伸缩性。

# 并发程序监控调优排查思路

# 同步性能的排查思路

测试程序的可伸缩性我们可以分配一些有效的工具进行排查,例如unix上的vmstat指令,当然这一切的前提是cpu核心都被充分的利用,如果cpu消耗发布不均匀就说明一组线程交由一小部分的处理器进行处理,当前程序压测结果存在以偏概全不足够权威,合理的排查思路为:

  1. 查看负载,负载是可以明确排查当前服务器负担的重要指标,如果这个值低于核心数*0.7,那么说明压测的样本不够多,需要补充数据集。
  2. IO密集型:通过iostat查看当前性能是否大量时间处于IO状态,如果是则服务器网络带宽不够需要提升带宽和网络IO性能以保证IO操作快速结束,尽可能让CPU快速处理尽可能多的线程任务。
  3. 外部限制:查看数据库等服务器性能负载情况以判断外部是否存在瓶颈。
  4. jstack查看等待的线程和锁竞争激烈程度。
  5. 16核处理器上只有4个线程,没有充分利用CPU处理器,未能及时利用资源提升程序的并发度。

# 尽可能不用对象池

这是一个比较传统的概念了,这里笔者也蛮扩展说明一下,现代java对象分配技术以优化到比C语言malloc调用更快了,在hotspot1.4.x和5.0中,差不多10条指令就可以完成一个对象分配,而且对象分配基本在线程本地内存块完成(TLAB技术),甚至在现代JIT优化有时候对象的分配直接在栈上完成分配,根本没有任何同步的协调,而对象池存在同步协调分配对象的阻塞开销,相比于前者不仅内存存在一定的占用,同时阻塞开销很可能是内存分配操作的数百倍,得不偿失。

# 小结

本文结合传统的优化方法论以及一些常见的优化手段示例详细的介绍了并发编程中的一些常见的优化方法和手段,希望对你有帮助。

你好,我是 SharkChili ,禅与计算机程序设计艺术布道者,希望我的理念对您有所启发。

📝 我的公众号:写代码的SharkChili
在这里,我会分享技术干货、编程思考与开源项目实践。

🚀 我的开源项目:mini-redis
一个用于教学理解的 Redis 精简实现,欢迎 Star & Contribute:
https://github.com/shark-ctrl/mini-redis (opens new window)

👥 欢迎加入读者群
关注公众号,回复 【加群】 即可获取联系方式,期待与你交流技术、共同成长!

# 参考

《java并发编程实战》

编辑 (opens new window)
上次更新: 2026/03/26, 01:05:31
浅谈Java并发编程中断的哲学
Java线程池知识点小结

← 浅谈Java并发编程中断的哲学 Java线程池知识点小结→

最近更新
01
Claude Code 记忆管理:CLAUDE.md 最佳实践
04-24
02
Claude Code 实战指南:从安装配置到企业级开发流程
04-20
03
一次 Claude Code 启动失败的 AI 辅助排查复盘
04-18
更多文章>
Theme by Vdoing | Copyright © 2025-2026 Evan Xu | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×
×