volatile定义:
volatile是Java提供的关键字,用来实现变量在多个线程之间共享的机制。用来修饰成员属性,final不能和volatile一起使用。
如果volatile变量修饰符使用恰当的话,它比synchronized的使用和执行成本更低,因为它不会引起线程上下文的切换和调度。它有线程可见性,禁止指令重排序的特性。
为何需要volatile?
在Java8规范文档中第8节中给了我们答案。
上面的意思就是说volatile是用于多个线程并发安全访问共享变量的机制。由于Jvm中每个线程都有自己的工作内存,并且操作变量都是操作工作内存,这种情况下如果多个线程对同一个变量操作会导致数据不一致,因此Jvm提供了volatile来解决这种问题。
volatile特性:
一、可见性
volatile是Java语言的一个关键字,可见性的能力是在jvm层面实现的。
可见性保证某一个线程对volatile修饰的变量的修改对其他线程立即可见。
二、有序性:禁止指令重排序
当程序执行到volatile变量的读或写时,在其前面的操作肯定全部已经执行完毕,且结果已经对后面的操作可见;在其后面的操作肯定还没有执行。
volatile实现原理:
volatile关键字在jvm层面会插入汇编语句lock指令,下面是openjdk8源码,在对volatile修饰的变量操作时,会插入内存屏障。
一、写volatile源码实现
if (cache->is_volatile()) {
if (tos_type == itos) {
obj->release_int_field_put(field_offset, STACK_INT(-1));
} else if (tos_type == atos) {
VERIFY_OOP(STACK_OBJECT(-1));
obj->release_obj_field_put(field_offset, STACK_OBJECT(-1));
OrderAccess::release_store(&BYTE_MAP_BASE[(uintptr_t)obj >> CardTableModRefBS::card_shift], 0);
} else if (tos_type == btos) {
obj->release_byte_field_put(field_offset, STACK_INT(-1));
} else if (tos_type == ltos) {
obj->release_long_field_put(field_offset, STACK_LONG(-1));
} else if (tos_type == ctos) {
obj->release_char_field_put(field_offset, STACK_INT(-1));
} else if (tos_type == stos) {
obj->release_short_field_put(field_offset, STACK_INT(-1));
} else if (tos_type == ftos) {
obj->release_float_field_put(field_offset, STACK_FLOAT(-1));
} else {
obj->release_double_field_put(field_offset, STACK_DOUBLE(-1));
}
//这是实现写volatile变量的关键,通过storeload屏障实现
OrderAccess::storeload();
- jvm层四个内存屏障定义
inline void OrderAccess::loadload() { acquire(); }
inline void OrderAccess::storestore() { release(); }
inline void OrderAccess::loadstore() { acquire(); }
inline void OrderAccess::storeload() { fence(); }
- storeLoad内存屏障实现
//storeload屏障的实现
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
}
}
通过jitwatch工具查看修改volatile变量时汇编指令如下:就是上面storeload屏障实现的。
下面看看Storeload屏障的作用:
总的来说Storeload屏障保证对volatile变量的修改对其他线程立即可见。
下次面试官问就知道了。
二、读volatile源码实现
if (cache->is_volatile()) {
if (tos_type == atos) {
VERIFY_OOP(obj->obj_field_acquire(field_offset));
SET_STACK_OBJECT(obj->obj_field_acquire(field_offset), -1);
} else if (tos_type == itos) {
SET_STACK_INT(obj->int_field_acquire(field_offset), -1);
} else if (tos_type == ltos) {
SET_STACK_LONG(obj->long_field_acquire(field_offset), 0);
MORE_STACK(1);
} else if (tos_type == btos) {
SET_STACK_INT(obj->byte_field_acquire(field_offset), -1);
} else if (tos_type == ctos) {
SET_STACK_INT(obj->char_field_acquire(field_offset), -1);
} else if (tos_type == stos) {
SET_STACK_INT(obj->short_field_acquire(field_offset), -1);
} else if (tos_type == ftos) {
SET_STACK_FLOAT(obj->float_field_acquire(field_offset), -1);
} else {
SET_STACK_DOUBLE(obj->double_field_acquire(field_offset), 0);
MORE_STACK(1);
}
}
cache->is_volatile(),表示如果变量i被volatile修饰,那么为true,接着获取变量i的值,操作由xxx(类型) _field_acquire方法实现。\
以int为例,来看看int_field_acquire方法的实现,这个方法在oop.inline.hpp中:
inline jint oopDesc::int_field_acquire(int offset) const {
return OrderAccess::load_acquire(int_field_addr(offset)); }
我们看到,内部调用了acquire方法,该方法在不同的系统环境中有不同的实现,我们来看看在linux中的实现,该实现在orderAccess_linux_x86.inline.hpp中:
inline jint OrderAccess::load_acquire(volatile jint* p) { return
*p; }
我们可以看到第一个参数加了关键字volatile
,这一这里的volatile属于C++的关键字,该关键字在C++中的作用如下:
volatile是一种类型修饰符,被volatile声明的变量表示随时可能发生变化,每次对变量的读取,都会从内存中重新加载
。并且编译器对操作该变量的代码不再进行优化,比如不再使用乱序优化,这也就是内存屏障的作用。
这里我们可以看到读volatile就是使用的C++的volatile关键字控制的,并没有手动插入编译器屏障
。我们也可以发现,实际上C++的volatile关键字和手动插入的编译器屏障【_ asm _ _ volatile _ ( " " : : : “memory” )】效果是一致的,能够禁止重排序,同时能够获取到最新的值。
volatile原理总结:
a) 读volatile:基于c++的volatile关键字,每次从主存中读取。
b) 写volatile:基于c++的volatile关键字和 lock addl指令的内存屏障,每次将新值刷新到主存,同时其他cpu缓存的值失效。
c) C++的volatile禁止对这个变量相关的代码进行乱序优化(重排序),也就具有内存屏障的作用了,另外Linux内核也可以手动插入内存屏障:_ asm _ _ volatile _ ( " " : : : “memory” )
d) 上面分析的是jvm层面的实现,其实可见性和有序性底层依赖cpu的特性,比如缓存一致性协议,总线锁等,这些知识需要对硬件知识有理解,笔者对这块了解的不够,有兴趣的读者可以去研究研究。
volatile带来的问题:
一、伪共享问题
修改volatile修饰的变量时,会强制刷新到主存。并且使��他工作线程中的变量缓存行失效,这样保证可可见性,但是会产生伪共享问题,计算机缓存系统,最小缓存单位是缓存行,如果多个变量存在同一个缓存行,如果修改有volatile修饰的变量,则修改时这整个缓存行就会失效。这个时候所有变量都重新从主存获取。影响整体性能。
Jvm虚拟机层面解决volatile带来的缓存行伪共享的方案。Jdk8提供了@sun.misc.Contended注解,解决伪共享问题,自动为volatile变量填充满缓存行,ConcurentHashMap中count计数器就用到该技术。
@sun.misc.Contended static final class CounterCell {
volatile long value; CounterCell(long x) {
value = x; }}
总结:
本次分析了volatile的功能,特性以及在Jvm层面的实现原理,volatile是在多线程访问共享变量时一致性访问的方案,它的性能比Synchronized更高,如果我们需要在多线程中访问共享遍历可以使用起来,但是注意它不保证操作原子性,修改的操作需要同步。