禅与计算机 禅与计算机
首页
  • 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关键字
      • 写在文章开头
      • 详解volatile关键字
        • 普通变量并发操作的不可见性
        • 最低线程安全和64位变量的风险
        • 通过volatile修饰保证可见性
        • 基于JMM模型详解volatile的可见性
        • 关于volatile可见性在硬件层面的分析
        • volatile无法保证原子性
        • volatile如何禁止指令重排序
      • 关于volatile一些更进一步的理解
        • volatile在并发场景中的性能表现和运用
        • volatile与并发编程中三个重要特性
      • 小结
      • 参考
    • 聊聊JVM中安全点的概念
    • 并发编程ThreadLocal必知必会
    • CompletableFuture基础实践小结
    • CompletableFuture异步IO密集型任务最佳实践
    • CompletableFuture异步多任务最佳实践
    • CompletableFuture组合流水线任务实践
    • 实现一个简单实用的的并发同步模型
    • 解决Java并发问题的常见思路
    • 来聊一个有趣的限流器RateLimiter
    • 硬核详解FutureTask设计与实现
    • 线程池大小设置的底层逻辑与场景化方案
    • 记一个ConcurrentHashMap使用不当导致的并发事故
    • JS设计模式总结笔记
    • 小程序笔记
    • 《JavaScript教程》笔记
  • JVM相关

  • 深入理解Spring框架

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

详解Java并发编程volatile关键字

@[toc]

# 写在文章开头

volatile被称之为轻量级的synchronized,即通过无锁的方式保证可见性,而本文将通过自顶向下的方式深入剖析这个关键字的底层实现,希望对你有帮助。

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

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

# 详解volatile关键字

# 普通变量并发操作的不可见性

我们编写一段多线程读写一个变量的代码,t1一旦感知num被t2修改,就会结束循环,然而事实却是这段代码即使在t2完成修改之后,t1也像是感知不到变化一样一直无限循环阻塞着:

	private static int num = 0;

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(2);

        Thread t1 = new Thread(() -> {
            while (num == 0) {

            }
            log.info("num已被修改为:1");
            countDownLatch.countDown();
        });


        Thread t2 = new Thread(() -> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            num++;
            log.info("t2修改num为1");
            countDownLatch.countDown();
        });

        t1.start();
        t2.start();
        countDownLatch.await();

        log.info("执行结束");
    }
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

# 最低线程安全和64位变量的风险

针对上述的情况,多线程在没有正确的同步情况下,可能拿到一个失效的变量值,但它并非是没有任何修改操作,我们称这种变量为最低线程安全,当然这种概念也仅仅是针对一些例如int这样的基本类型。

若是64位例如double和long,因为JMM内存模型上规定了该变量操作在不同的处理器上进行运算操作,这就是的64位操作无法保证原子性,更谈不上最低线程安全性了。

# 通过volatile修饰保证可见性

于是我们将代码增一个本文所引出的关键字volatile 加以修饰:

private volatile static int num = 0;
1

对应的我们给出输出结果,如预期一样线程修改完之后线程1就会感知到变化而结束循环,由此可知volatile关键字的第一个语义——保证并发场景下共享变量的可见性:

23:54:04.040 [Thread-0] INFO MultiApplication - num已被修改为:1
23:54:04.040 [Thread-1] INFO MultiApplication - t2修改num为1
23:54:04.042 [main] INFO MultiApplication - 执行结束
1
2
3

# 基于JMM模型详解volatile的可见性

实际上,volatile底层实现和JMM内存模型规范息息相关,该模型规范了线程的本地变量(各个线程拿到共享变量num的副本)和主存(内存中的变量num)的关系,其规范通过happens-before等规约强制规范了JVM需要针对这几个原则要求做出相应的处理来配合处理器保证共享变量操作的可见性和有序性,这一点感兴趣的读者可以移步下面这篇文章了解一下JMM内存规范和避免指令重排序的实际落地实现:

按照JMM模型抽象的各种happens-before及其内存模型8大操作:

volatile的变量的写操作, happens-before后续读该变量的代码

这就要求t1和t2修改num的时候,都必须从主存中先加载才能进行修改,以上述代码为例,假设t1修改了num的值,完成后就必须将最新的结果写回主存中,而t2收到这个修改的通知后必须从主内存中拉取最新的结果才能进行操作:

关于JMM更多知识,感兴趣的读者可以看看笔者这篇文章:

详解JMM内存模型:https://mp.weixin.qq.com/s/r7e6J-Pch7pEd-iMrC4NJA (opens new window)

上述这个流程只是JMM模型的抽象,也就是JVM便于让程序员理解的一种抽象模型而实际的落地, 所以为了更好理解volatile关键字修饰的变量,我们还是以上述的例子了解一下

private volatile static int num = 0;

    public static void main(String[] args) throws InterruptedException {
        num = 24;
        num++;
    }
1
2
3
4
5
6

对应的我们给出JIT后的汇编码:

#  num = 24;
0x000000000368cd76: mov $0x18,%edi # 将24加载到edi寄存器
0x000000000368cd7b: mov %edi,0x68(%rsi) # 将edi寄存器的值存储到内存地址为[rsi + 0x68] 也就是变量num
0x000000000368cd7e: lock addl $0x0,(%rsp)  ;*putstatic num
                                           ; - org.example.Main::main@2 (line 13) # lock前缀起到类似内存屏障的作用,保证num=24这个写操作对内存中所有的处理器可见
                                           
# num++;                                     
0x000000000368cd83: mov 0x68(%rsi),%edi  ;*getstatic num
                                         ; - org.example.Main::main@5 (line 14) # 将num值加载到edi寄存器
0x000000000368cd86: inc %edi # 基于increase将寄存器上的值也就是24加上1
0x000000000368cd88: mov %edi,0x68(%rsi) # 将edi寄存器上的值赋值给num
0x000000000368cd8b: lock addl $0x0,(%rsp)  ;*putstatic num
                                           ; - org.example.Main::main@10 (line 14) # 基于lock前缀实现JMM规范中的写回主存中,保证所有线程可见
1
2
3
4
5
6
7
8
9
10
11
12
13

针对num赋值为24这操作,汇编指令执行了如下三步:

  1. 通过mov $0x18,%edi将24(0x18)加载到edi寄存器。
  2. mov %edi,0x68(%rsi)将这个24复制给num。
  3. 重点来了,num=24即位于main 23行的代码,它的字节码为putstatic num这步本质就是完成变量的赋值,实际上在完成变量赋值之后,它通过lock前缀指令起到一个内存屏障的作用,保证上述的赋值操作对于所有的处理器可见,也就是实现JMM规范中的写入主存操作(下文会从硬件层面分析该指令),由此保证num++操作时会先通过getstatic 到主存中获取最新值到本地内存中完成自增操作。

同样的num++也是同理,可以看到对应注释的汇编码,在完成自增即inc 操作后,同样执行lock前缀指令将数据写入主存。

# 关于volatile可见性在硬件层面的分析

上文我们以JMM规范粗略的讲解了lock前缀在规范层面上的可见性,查阅IA-32架构软件开发者手册可知,lock前缀的指令在多核处理器下会引发了两件事情:

  1. 将当前变量num从当前处理器的缓存行(cache-line)数据写回内存。
  2. 此时,硬件层面上执行当前的CPU会通知其他处理器该变量已被修改,其他处理器cache-line中的num值全部变为invalid(无效)。

这也就是我们Intel 64著名的MESI协议,将该实现代入我们的代码,假设线程1的num被CPU-0的处理,线程2被CPU-1处理,实际上底层的实现是:

  1. t1获取共享变量num的值,此时并没有其他核心上的线程获取,状态为E(exclusive)。
  2. t2启动也获取到num的值,此时总线嗅探到另一个CPU也有这个变量的缓存,所以两个CPU缓存行都设置为S(shard)。
  3. t2修改num的值,通过总线嗅探机制发起通知,t1的线程收到消息后,将缓存行变量设置为I(invalid)。
  4. t1需要输出结果,因为看到自己变量是无效的,于是通知总线让t1将结果写回内存,自己重新加载。

更多关于MESI协议的实现细节,感兴趣的读者可以参考笔者的这篇文章:https://mp.weixin.qq.com/s?__biz=MzkwODYyNTM2MQ==&mid=2247486863&idx=1&sn=58dd09b52e16fa59d7eacab0487373ee&chksm=c0c65931f7b1d0278ac049b2ce3f59bf7c839245c3205fddbff45886b7b8ad04852bdbbc6472#rd (opens new window)

# volatile无法保证原子性

我们不妨看看下面这段代码,首先我们需要了解一下的:

private static volatile int num;

    public static void main(String[] args) throws InterruptedException {
        num++;
    }
1
2
3
4
5

因为这段代码位于笔者IDE的13行,基于该信息笔者拿到对应的字节码,可以看到num++这个操作在底层实现如下,大体来说分为三步:

  1. GETSTATIC 读取num的值推到栈顶。
  2. ICONST_1将常量1压入操作数栈。
  3. IADD将栈顶的num和1进行相加。
  4. 写回内存中PUTSTATIC 写回主存。
LINENUMBER 13 L0
    GETSTATIC org/example/Main.num : I
    ICONST_1
    IADD
    PUTSTATIC org/example/Main.num : I
1
2
3
4
5

更进一步,基于jitwatch,我们看到的对应的汇编码如下,同样可以看到读取、自增、写回操作:

0x00000000038ca096: mov 0x68(%r10),%r8d
0x00000000038ca09a: inc %r8d
0x00000000038ca09d: mov %r8d,0x68(%r10)
1
2
3

很明显一个自增操作是由多条指令完成,这也就意味着,在上述指令执行期间,很可能出现其他线程读取到自增后但是还未写到内存的过期值:

这里蛮补充一句,关于jitwatch的安装使用,感兴趣的读者可以参考这篇文章:https://mp.weixin.qq.com/s/RDxQxVBx0X34qu_QYlPglg (opens new window)

我们查看代码的运行结果,可以看到最终的值不一定是10000,由此可以得出volatile并不能保证原子性

public class VolatoleAdd {
    private static int num = 0;


   public void increase() {
        num++;
    }


    public static void main(String[] args) {

        int size = 10000;
        CountDownLatch downLatch = new CountDownLatch(1);
        ExecutorService threadPool = Executors.newFixedThreadPool(size);
        VolatoleAdd volatoleAdd = new VolatoleAdd();
        for (int i = 0; i < size; i++) {
            threadPool.submit(() -> {
                try {
                    downLatch.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                volatoleAdd.increase();


            });
        }

        downLatch.countDown();
        threadPool.shutdown();

        while (!threadPool.isTerminated()) {

        }

        System.out.println(VolatoleAdd.num);//9998

    }
}

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

而对应的解决方案我们可以通过synchronized、原子类、或者Lock相关实现类解决问题。

# volatile如何禁止指令重排序

而volatile不仅可以保证可见性,还可以避免指令重排序,底层同样是通过JMM规约,禁止特定编译器进行有风险的重排序,以及在生成字节序列时插入内存屏障避免CPU重排序解决问题。

我们不妨看一段双重锁校验的单例模式代码,代码如下所示可以看到经过双重锁校验后,会进行new Singleton();

public class Singleton {

    private static Singleton uniqueInstance;

    private Singleton() {
    }

    public  static Singleton getUniqueInstance() {
        //先判断对象是否已经实例过,没有实例化过才进入加锁代码
        if (uniqueInstance == null) {
            //类对象加锁
            synchronized (Singleton.class) {
                if (uniqueInstance == null) {
                    uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

这一操作,这个对象创建的操作乍一看是原子性的,实际上编译后再执行的机器码会将其分为3个动作:

  1. 为引用uniqueInstance分配内存空间
  2. 初始化uniqueInstance
  3. uniqueInstance指向分配的内存空间

所以如果没有volatile 禁止指令重排序的话,1、2、3的顺序操作很可能变成1、3、2,进而可能出现下面这种情况:

  1. 线程1执行步骤1分配内存空间。
  2. 线程1执行步骤3让引用指向这个内存空间。
  3. 线程2进入逻辑判断发现uniqueInstance不为空直接返回,导致外部操作异常。

极端情况下,这种情况可能导致线程2外部操作到的可能是未初始化的对象,导致一些业务上的操作异常:

所以针对这种情况,我们需要增加volatile 关键字让禁止这种指令重排序:

private volatile  static Singleton uniqueInstance;
1

按照JMM的happens-before原则volatile的变量的写操作, happens-before后续读该变量的代码,这就会使的volatile操作可能实现如下几点:

  1. 第二个针对volatile写操作时,不管第一个操作是任何操作,都不能发生重排序。
  2. 第一个针对volatile读的操作,后续volatile任何操作都不能重排序。
  3. 第一个volatile写操作,后续volatile读,不能进行重排序。

基于这套规范,在编译器生成字节码时,就会通过内存屏障的方式告知处理器禁止特定的重排序:

  1. 每个volatile写后插入storestore,让第一个写优先于第二个写,避免重排序后的写(可以理解未变量计算)顺序重排序导致的计数结果异常。
  2. 每个volatile写后插入storeload,让第一个写先于后续读,避免读取异常。
  3. 每个volatile读后加个loadstore,让第一个读操作先于第二个写,避免读写重排序的异常。
  4. 每个volatile读后加个loadload,让第一个读先于第二个读,避免读取顺序重排序的异常。

回过头来,对于内存屏障的实现,以我们的单例模式初始化对象实例来说,其硬件架构的实现上,这个new的操作涉及多条指令,在处理器执行时可能会不按照规定顺序交由不同的电路单元执行,这就可能出现上述所谓1、3、2的情况。

对应的我们给出相应的汇编指令,可能看到其核心执行步骤为如下三步:

  1. 调用JVM内部函数,在堆内存上分配Singleton内存并完成对象创建,也就是在堆内存中创建单例instance对象。
  2. 获取静态变量存储位置到r11上,即将元空间的静态变量instance放到寄存器上为后续将步骤1所new的对象分配给该引用做好准备。
  3. 通过cmpxchg 源自指令比对r11对应的引用instance是否为null,若为null则说明没有被其他线程初始化过,则将r10创建的对象分配到该引用上,同时基于lock前缀将该引用的最近创建结果写入内存,交由CPU硬件层面的MESI协议让其他处理器可以看到最新结果。

对于避免指令重排序的语义,我们同第三条指令就能理解,即lock需要将更新操作写入内存这一特性,保证lock前缀之上的步骤1和步骤2的操作都必须完成之后,才能执行原子性的将创建的对象赋值给静态变量instance的操作,即通过硬件层面的lock前缀保证有数据的情况下才能完成对象复制,从而形成一种指令无法超越内存屏障的效果,由此具备避免指令重排序的语义:

# 调用JVM内部函数,在堆内存上分配Singleton内存并完成对象创建
0x0000000003d9300f: callq 0x00000000039057a0  ; OopMap{off=372}
                                              ;*new  ; - org.example.Singleton::getUniqueInstance@17 (line 16)
                                              ;   {runtime_call}
                                                                                           
0x0000000003d93014: int3  ;*new  ; - org.example.Singleton::getUniqueInstance@17 (line 16)
# 获取静态变量存储位置到r11上,即将元空间的静态变量instance放到寄存器上
             L0009: movabs $0x76b95d828,%r11  ;   {oop(a 'java/lang/Class' = 'org/example/Singleton')}
#  保证上述操作完成后,通过cmpxchg 源自指令比对r11对应的引用instance是否为null,若为null则说明没有被其他线程初始化过,则将r10创建的对象分配到该引用上,同时基于lock前缀做到一个类似内存屏障的作用,由此避免指令重排序
0x0000000003d9301f: lock cmpxchg %r10,(%r11)
# 执行后续操作
1
2
3
4
5
6
7
8
9
10
11

# 关于volatile一些更进一步的理解

# volatile在并发场景中的性能表现和运用

关于volatile性能的讨论,实际上在jdk8以上synchronized 关键字的锁升级的优化机制上很说明两者的差异,我们大体只能得出如下三个结论:

  1. 相较于普通变量num和加上volatile修饰后的普通变量num,因为后者存在一致性问题需要lock前缀写回主存,所以后者性能表现比普通变量表现差。
  2. 对于单线程修改,多线程读取并发共享变量的场景,我们更建议使用volatile,尽可能避免高并发场景下单修改多读取变量的重量级锁开销。
  3. 对于并发修改,建议使用volatile配合锁来保证可见性和数据一致性。

# volatile与并发编程中三个重要特性

即原子性、有序性、可见性:

  1. 原子性:一组操作要么全部都完成,要么全部失败,Java就是基于synchronized或者各种Lock实现原则性。
  2. 可见性:线程对于某些变量的操作,对于后续操作该变量的线程是立即可见的。Java基于synchronized或者各种Lock、volatile实现可见性,例如声明volatile变量这就意味着Java代码在操作该变量时每次都会从主内存中加载。
  3. 有序性:指令重排序只能保证串行语义一致性,并不能保证多线程情况下也一致,Java常常使用volatile禁止指令进行重排序优化。

# 小结

至此我们从几个简单的实践案例和volatile底层汇编码等多个角度为该关键字进行深入分析,希望对你有帮助。

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

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

# 参考

CPU 缓存一致性:https://xiaolincoding.com/os/1_hardware/cpu_mesi.html#cpu-cache-的数据写入 (opens new window)

volatile可见性实现原理:https://blog.csdn.net/itakyubi/article/details/100527743 (opens new window)

吃透Java并发:volatile是怎么保证可见性的:https://zhuanlan.zhihu.com/p/250657181 (opens new window)

volatile 三部曲之可见性:https://mp.weixin.qq.com/s/2tuUq1QOtfhARfXh5VQx8A (opens new window)

透写和回写缓存(Write Through and Write Back in Cache):https://zhuanlan.zhihu.com/p/571429282 (opens new window)

编辑 (opens new window)
上次更新: 2026/03/26, 01:05:31
深入解析CopyOnWriteArrayList
聊聊JVM中安全点的概念

← 深入解析CopyOnWriteArrayList 聊聊JVM中安全点的概念→

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