volatile是java自带的关键字,其作用是通过防止指令重排和缓存一致性协议,保证多线程并发下的可见性问题。指令重排是指,在不影响代码执行的最终结果前提下,为了最大化cpu利用率以及性能,将代码乱序执行。
确切来讲,volatile并不能保证缓存一致性,缓存一致性是通过硬件层面的缓存一致性协议保证的,例如MESI协议。但是有些硬件是具有缓存强一致性的,也就是说在这些平台上,即使是没有被volatile修饰的变量也不会存在缓存一致性问题,在这些平台上面,volatile只是一条空指令。
保证缓存一致性主要有两种方法,一种是cpu的缓存锁(总线锁),一种是缓存一致性协议。缓存锁的作用原理是,当发起读写操作时,处理器会发出一个lock指令锁住总线,使当前处理器独享内存,其他处理器在lock期间的请求将会被阻塞。所以,这种方法会导致性能问题。缓存一致性协议是指通过一定的协议,解决缓存一致性问题,至于如何具体解决,这就要去了解各种缓存一致性协议的具体实现。
缓存一致性协议最出名的是Intel的MESI协议,MESI协议保证了每个缓存中使用的共享变量的副本是一致的。它核心的思想是:当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。
指令重排分有两种,一种是编译器的优化乱序,一种是cpu的执行乱序。对于这两种种情况,可以通过优化屏障和内存屏障。
内存屏障,也称内存栅栏,内存栅障,屏障指令等,是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作。内存屏障只解决顺序一致性问题,不解决缓存一致性问题,缓存一致性问题是有cpu的缓存锁或者缓存一致性协议保证的。
CPU级别的内存屏障有三种,分别为写屏障(store barrier),读屏障(load barrier)和全屏障(full barrier)。
和cpu内存屏障不同的是,编译器级别内存屏障分为四种,如图。
在jvm源码里面,不同的内核对内存屏障的实现不同,例如在orderAccess_linux_x86.inline.hpp
里面,定义了这四种内存屏障对应的调用方法:
inline void OrderAccess::loadload() { acquire(); }
inline void OrderAccess::storestore() { release(); }
inline void OrderAccess::loadstore() { acquire(); }
inline void OrderAccess::storeload() { fence(); }
从上面的代码可以看到,loadload barrier和loadstore barrier调用的方法都是一样的,也就是说,loadload barrier和storeload barrier的作用是一样的。
带volatile的变量字节码
private static volatile int num;
descriptor: I
flags: ACC_PRIVATE, ACC_STATIC, ACC_VOLATILE
不带volatile的变量字节码
private static int num;
descriptor: I
flags: ACC_PRIVATE, ACC_STATIC
从上面二者的字节码可以看出来,volatile修饰的变量多了个ACC_VOLATILE标志,而在accessFlags.hpp
里面,定义了诸多判断java各种标识,其中就有bool is_volatile () const { return (_flags & JVM_ACC_VOLATILE ) != 0; }
,这个方法就是判断变量是否是volatile变量。这个方法会在操作volatile变量的时候调用,例如在bytecodeInterpreter.hpp
里面,当对一个变量执行写操作时,代码如下:
int field_offset = cache->f2_as_index();
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) {
...
}
OrderAccess::storeload();
} else {
if (tos_type == itos) {
obj->int_field_put(field_offset, STACK_INT(-1));
} else if (tos_type == atos) {
VERIFY_OOP(STACK_OBJECT(-1));
obj->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) {
...
}
}
可以看到,如果是volatile,会调用对应的带release开头的方法,并且在执行最后,会执行OrderAccess::storeload();
这一行代码,也就是在每一次的volatile写之后插入一个storeload屏障。
扩展阅读