12.3.4 针对long和double型变量的特殊规则

12.3.4 针对long和double型变量的特殊规则

Java内存模型要求lock、unlock、read、load、assign、use、store、write这八种操作都具有原子性, 但是对于64位的数据类型(long和double),在模型中特别定义了一条宽松的规定:允许虚拟机将没有被volatile修饰的64位数据的读写操作划分为两次32位的操作来进行,即允许虚拟机实现自行选择是否要保证64位数据类型的load、store、read和write这四个操作的原子性,这就是所谓的“long和double的非原子性协定”(Non-Atomic Treatment of double and long Variables)。

如果有多个线程共享一个并未声明为volatile的long或double类型的变量,并且同时对它们进行读取和修改操作,那么某些线程可能会读取到一个既不是原值,也不是其他线程修改值的代表了“半个变量”的数值。不过这种读取到“半个变量”的情况是非常罕见的,经过实际测试[^1],在目前主流平台下商用的64位Java虚拟机中并不会出现非原子性访问行为,但是对于32位的Java虚拟机,譬如比较常用的32 位x86平台下的HotSpot虚拟机,对long类型的数据确实存在非原子性访问的风险。从JDK 9起, HotSpot增加了一个实验性的参数-XX:+AlwaysAtomicAccesses(这是JEP 188对Java内存模型更新的一部分内容)来约束虚拟机对所有数据类型进行原子性的访问。而针对double类型,由于现代中央处理器中一般都包含专门用于处理浮点数据的浮点运算器(Floating Point Unit,FPU),用来专门处理单、双精度的浮点数据,所以哪怕是32位虚拟机中通常也不会出现非原子性访问的问题,实际测试也证实了这一点。笔者的看法是,在实际开发中,除非该数据有明确可知的线程竞争,否则我们在编写代码时一般不需要因为这个原因刻意把用到的long和double变量专门声明为volatile。

[^1]: 根据一篇文章(https://shipilev.net/blog/2014/all-accesses-are-atomic/)的介绍,对于这种情况,在 ARMv6、ARMv7、x86、x86-AMD64、PowerPC等平台上都进行了实际测试。