禅与计算机 禅与计算机
首页
  • 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基础

  • 并发编程

  • JVM相关

    • 从零开始掌握 JVM
    • JVM核心知识点小结
    • JVM指令集概览:基础与应用
    • JDK 方法区变迁史:版本间的差异与改进
    • JVM类加载器深度解析
    • JVM方法区深度解析
    • Java内存模型JMM详解
      • 写在文章开头
      • 详解指令重排序问题
        • 什么是重排序问题
        • 编译器重排序
        • 指令重排序
        • 内存重排序
        • 如何避免指令重排序
      • 详解Java内存模型JMM
        • 什么是JMM模型
        • JVM和JMM有什么区别
        • 什么是happens-before原则?常见的happens-before原则有哪些?
        • happens-before和JMM有什么关系
        • JMM规范如何解决处理器指令重排序问题
      • 小结
      • 参考
    • Java对象大小的精确计算方法
    • 来聊聊大厂面试常问的指针压缩
    • 逃逸分析在Java中的应用与优化
    • 从零开始理解JVM的JIT编译机制
    • JITWatch工具使用指南
    • G1垃圾回收器:原理详解与调优指南
    • 写给新手的一个JVM调优入门级案例
    • JVM故障排查实战指南
    • JVM内存问题排错最佳实践
    • JVM内存溢出排查指南
    • MAT内存分析工具安装配置
    • 简明的Arthas使用教程
    • 简明的Arthas配置及基础运维教程
    • 基于Arthas Idea的JVM故障排查与指令生成
    • 基于arthas量化监控诊断java应用方法论与实践
    • 深入剖析arthas技术原理
    • JVM系列文章汇总
  • 深入理解Spring框架

  • Java核心技术
  • JVM相关
sharkchili
2026-03-25
目录

Java内存模型JMM详解

[toc]

# 写在文章开头

本文将着重从JMM指令规范以及如何解决程序可见性和有序性两个问题为入口,为读者深入剖析JMM内存模型,希望对你有帮助。

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

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

# 详解指令重排序问题

# 什么是重排序问题

代码在执行过程从,计算机的不同层级为了提高最终指令执行效率,都可能会对执行响应重排序,以Java程序为例,从编译到执行会经历:

  1. 生成指令阶段:编译器重排,该阶段JMM通过禁止特定类型的编译器重排序达到要求。
  2. 处理器阶段:处理器阶段存在指令并行重排序和内存系统加载重排序,这种处理器级别的重排序问题,则是要求编译器在生成指令阶段通过插入内存屏障即memory barriers指令禁止特定方式重排序。

# 编译器重排序

编译器(包括 JVM、JIT 编译器等)重排序即不影响单线程执行结果的情况下,会针对性的重排代码的效率以提高单线程情况下代码执行效率。当然这种重排序可能也会存在一些问题,假设我们现在有这样一段代码:

  1. 两个CPU核心加载到一段先初始化localNum
  2. 各自分别用用变量x、y读取读取对方的localNum的值

如下图所示:

极端情况,假设两个CPU都发生编译器重排序就可能出现CPU-0先执行x=lcalNum2,CPU-1执行y=lcalNum1,因为这两个本地变量初始化赋值指令被重排序,导致x、y最终被设置为0:

对于这种情况,JMM会针对性发生这种重排序的编译器进行禁止来解决这种问题。

# 指令重排序

现代的处理器会对某些指令进行重叠执行(采用指令级并行技术(Instruction-Level Parallelism,ILP),亦或者在不影响执行结果的情况下会允许Java字节码对应的机器码指令进行顺序调换以提高单线程下代码的执行效率,这种问题的表象和上述情况类似,这里也就不再演示了。

# 内存重排序

该方式排序并不是真正意义上的重排序,即处理器为了提升程序的处理效率,会将内存中的数据先加载到自己的cache line上,这使得并发场景下CPU本地内存数据可能与内存中的数据不一致的情况,在JMM上常常表现为主存和本地内存的数据不一致。

如下图,两个CPU同时从内存中加载到x为0,然后cpu-0执行程序中的累加指令,在cpu-0未将指令下回内存时,就短暂的出现数据不一致的情况:

# 如何避免指令重排序

这一点其实在上述各种重排序都已经简单的说明了:

  1. 对于编译器,会禁止特定类型的编译器重排序来避免编译器重排序在多线程情况下带来的问题。
  2. 对于指令重排序即处理器重排序,JVM生成程序指令序列时,会根据情况插入特定的内存屏障(Memory Barrier)来相关指令来告知处理器避免特定类型的指令重排序。

# 详解Java内存模型JMM

# 什么是JMM模型

为了屏蔽不同操作系统之间操作系统内存模型的差异,Java定义了属于自己的内存模型规范解决这个问题。 JMM也可以理解为针对Java并发编程的一组规范,抽象了线程和主内存之间的关系,以类似于volatile、synchronized等关键字以解决并发场景下重排序带来的问题。

JMM规定所有示例对象都必须放置在主存中,所以每个线程需要操作这些数据时就需要将数据拷贝一份到本地内存中在进行相应的操作。

而每个Java将主存中拷贝的变量在完成操作后写回主存中会经历以下过程:

  1. lock:首先将变量锁住,将这个共享变量设置为线程独占变量。
  2. read:将主存的共享变量读取到本地内存中。
  3. load:将变量load拷贝一份到本地内存中生成共享变量的副本。
  4. use:将共享变量副本放到执行引擎中。
  5. assign:将共享变量副本赋值给本地内存的变量。
  6. store:将变量放到主内存中
  7. write:写入主内存对应变量中
  8. unlock:解锁,该共享变量此时就可以被其他线程操作了。

同时,JMM模型还规定这些操作还得符合以下规范:

  1. 线程没有发任何assign操作的变量不可以写回主内存中。
  2. 新的变量只能在主内存中诞生。这就意味的线程中的变量必须是通过load从主存加载后再通过assign得到的。
  3. 一个线程通过lock锁定主内存变量共享变量时,这个线程可以对其上无数次锁(即线程可重入),其他线程就不能在对其上锁了。
  4. 一个线程没有lock一个共享变量,就不能对其进行unlock。
  5. 在执行use操作前,必须清空本地内存,通过load或者assign初始化变量值才可操作本地变量。

# JVM和JMM有什么区别

JVM规定了运行时的java程序的内存区域划分,例如实例对象必须放置在堆区等。

而JMM则决定了线程和和主内存之间的关系,例如共享变量必须存放在主内存中。通过定义一系列规范和原则简化用户实现并发编程的种种操作且确保Java代码从编译到转为CPU机器码执行结果都是准确无误的,也就是说JMM是一种内存模型语义的抽象并非实际的内存模型。

# 什么是happens-before原则?常见的happens-before原则有哪些?

happens-before也是一种JMM内存模型用来阐述内存可见性的一种规约,对应的happens-before原则共有8条,而常见的有以下5条:

  1. 程序顺序规则 :写前面的变量happens-before于后面的代码。
  2. 传递规则: A happens-before B,B happens-before C,那么A happens-before C。
  3. volatile 变量规则: volatile的变量的写操作, happens-before后续读该变量的代码。
  4. 线程启动规则 :Thread的start都有先于后面对于该线程的操作。
  5. 解锁规则:对一个锁的解锁操作happens-before对这个锁的加锁操作。

对于不会影响单线程或者多线程指令重排序操作java编译器不做要求,即不会过分干预编译器和处理器的大部分优化操作,例如下面这段代码,在单线程情况下,因为两者声明没有任何关联,处理器为了提高程序执行的并行度完全可以允许其以任意顺序执行,这也就是我们常说的as-if-serial,即没有强关联的指令,处理器可以根据自己的优化算法执行,任意重排序,对外结果好像就是串行执行一样:

而对于某些场景, JMM对于编译器或处理的某些会影响指令重排序的操作进行禁止,如下所示,getOne和getTwo先于最后计算,计算依赖于前两个变量,操作即两个get操作happens-before于最后的计算,但是两个get操作没有强关联,所以JVM这两段代码进行指令重排序的时候,JMM是允许的,所以执行时getTwo可能会先于getOne执行。

 public static void main(String[] args) {
        int one = getOne();//1
        int two = getTwo();//2
        System.out.println(one + two);//3
    }

    private static int getOne() {
        return 1;
    }

    private static int getTwo() {
        return 2;
    }

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

与之相反就是最后的计算,因为依赖于前两个get,所以JMM模型是明确要求禁止这种情况,于是就提出了happens-before原则,即写前面的变量happens-before于后面的代码以及A happens-before B,B happens-before C,那么A happens-before C,按照我们的例子就是每一个get操作都会按照顺序写,因为1操作先于2先于3,所以最终执行顺序就是1、2、3。

# happens-before和JMM有什么关系

JMM原则和禁止重排序的遵循的准则都是基于 happens-before准则要求,也就是要求针对编译器的指令重排序必须根据该准则通过某种方式落实,最常见的方式就是在生成执行指令前插入内存屏障,避免处理器进行危险的指令重排序。 所以,程序员只需理解happens-before原则的抽象即可理解可见性,由此避免去理解底层编译器和处理器的复杂实现:

# JMM规范如何解决处理器指令重排序问题

为了保证内存可见性,编译器在生成指令指令序列时通过内存屏障指令来禁止特定类型的处理器重排序问题,对应的屏障指令有:

  1. loadload:先加载load1先于后load2的操作,保证load1读取的数据结果对于load2可见。
  2. loadstore:load1的操作先于后store,保证store2的操作可以看见load1读取数据的最新结果。
  3. storestore:store1写入操作先于store2,保证store1的写入操作结果对于store2可见。
  4. storeload:先store的操作对于后load可见,即store操作变量的结果对于后续的load是可见的。

而本质上这些内存屏障在硬件层也就是Load Barrier和Store Barrier两个屏障,大体来说内存屏障的主要作用有:

  1. 组织屏障前后两个指令重排序。
  2. 强制把处理器高速缓冲区数据更新结果写回主内存,让其它处理器中缓存数据失效,这也就是大名鼎鼎的MESI协议。

对于Load Barrier而言,若在指令前插入Load Barrier,该屏障可读取数据时强制要求处理器将本地cache line设置为无效,直接从内存中读取数据:

而Store Barrier则是强制要求cpu cache line写入操作要直接从本地cache line强制刷新到内存中让其它核心中的cache line数据失效,而JMM规范就是基于这两个硬件屏障的多种组合保证了操作可见性:

对于java这门语言而言,内存屏障最经典的运用无非是volatile关键字,可以看到下面这段代码,为了保证volatile变量的可见性,即:

  1. 在volatile写的前后分别加入了loadstore和storeload,保证读取依赖数据后在执行写入并更新至主存
  2. 在volatile变量读前后分别加入loadload和loadstore保证读取到正确的数据在执行后续的写,即后续的写入操作对于volatile变量可见
 private static int normalData;
    private static volatile boolean volatileData = false;// volatile确保StoreLoad语义


    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            normalData = 1;
            //插入loadload屏障,保证上述数据改变可见
            volatileData = true;
            //插入storeload屏障,保证上述数据写入改变可见
        });

        Thread thread2 = new Thread(() -> {
            //插入loadload屏障,保证volatile读可见之前的读
            while (!volatileData) {
                //插入loadstore屏障,保证后续写可见volatile变量结果
            }
            System.out.println(normalData);
        });
        thread1.start();
        thread2.start();
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 小结

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

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

# 参考

JMM(Java 内存模型)详解:https://javaguide.cn/java/concurrent/jmm.html#从-cpu-缓存模型说起 (opens new window)

《Java并发编程的艺术》

内存屏障:https://www.jianshu.com/p/2ab5e3d7e510 (opens new window)

一文讲明白内存重排序:https://cloud.tencent.com/developer/article/1857174 (opens new window)

全知乎最详细的并发研究之CPU缓存一致性协议(MESI)有这一篇就够了:https://zhuanlan.zhihu.com/p/467782159 (opens new window)

编辑 (opens new window)
上次更新: 2026/03/26, 01:05:31
JVM方法区深度解析
Java对象大小的精确计算方法

← JVM方法区深度解析 Java对象大小的精确计算方法→

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