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重排序会导致处于半初始化状态就被外部所引用