volatile关键字
1.volatile的特性#
1.多线程之间可见性
2.禁止指令重排序
volatile修饰的内存,不可以重排序,对volatile修饰变量的读写访问,都不可以换顺序
2. volatile和锁的内存语义#
从内存语义的角度来说,volatile的写-读与锁的释放-获取有相同的内存效果:volatile写和锁的释放有相同的内存语义;volatile读与锁的获取有相同的内存语义。
3. volatile写-读的内存语义#
volatile写的内存语义:当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存。
volatile读的内存语义:当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。
如果我们把volatile写和volatile读两个步骤综合起来看的话,在读线程B读一个volatile变量后,写线程A在写这个volatile变量之前所有可见的共享变量的值都将立即变得对读线程B可见。
下面对volatile写和volatile读的内存语义做个总结
线程A写一个volatile变量,实质上是线程A向接下来将要读这个volatile变量的某个线程发出了(其对共享变量所做修改的)消息。
线程B读一个volatile变量,实质上是线程B接收了之前某个线程发出的(在写这个volatile变量之前对共享变量所做修改的)消息。
线程A写一个volatile变量,随后线程B读这个volatile变量,这个过程实质上是线程A通过主内存向线程B发送消息。
4. volatile实现细节#
-
字节码层面
ACC_VOLATILE
-
JVM层面
通过内存屏障
volatile写
StoreStoreBarrier
volatile 写操作
StoreLoadBarrier
volatile读
LoadLoadBarrier
volatile 读操作
LoadStoreBarrier
-
hotspot实现
bytecodeinterpreter.cpp
int field_offset = cache->f2_as_index(); if (cache->is_volatile()) { if (support_IRIW_for_not_multiple_copy_atomic_cpu) { OrderAccess::fence(); }
orderaccess_linux_x86.inline.hpp
inline void OrderAccess::fence() { if (os::is_MP()) { // always use locked addl since mfence is sometimes expensive #ifdef AMD64 __asm__ volatile ("lock; addl $0,0(%%rsp)" : : : "cc", "memory"); #else __asm__ volatile ("lock; addl $0,0(%%esp)" : : : "cc", "memory"); #endif } }
volatile底层使用lock指令来实现
lock指令用于在多处理器中执行执行时对共享内存的独占使用,它的作用是能够将当前处理器对应缓存的内容刷新到内存中并通知其他处理器对应的缓存失效,另外还提供了禁止处理器指令重排的功能
5.DCL单例为什么要加volatile#
因为在对象的创建过程中涉及到半初始化状态
比如Object obj = new Object()创建的过程
1.申请内存
2.初始化对象,赋予默认值
3.将obj指向内存地址
其中2和3可以进行重排序,如果DCL单例不加volatile,那么2和3重排序会导致处于半初始化状态就被外部所引用
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端