JVM内存模型中定义了8中原子操作: lock:将一个变量标识为被一个线程独占状态unclock:将一个变量从独占状态释放出来,释放后的变量才可以被其他线程锁定read:将一个变量的值从主内存传输到工作内存中,以便随后的load操作load:把read操作从主内存中得到的变量值放入工作内存的变量的副本中use:把工作内存中的一个变量的值传给执行引擎,每当虚拟机遇到一个使用到变量的指令时都会使用该指令assign:把一个从执行引擎接收到的值赋给工作内存中的变量,每当虚拟机遇到一个给变量赋值的指令时,都要使用该操作store:把工作内存中的一个变量的值传递给主内存,以便随后的write操作write:把store操作从工作内存中得到的变量的值写到主内存中的变量其中,与赋值,取值相关的包括 read,load,use,assign,store,write按照这个规定,long的读写都是原子操作,与我们的实践结果相反,为什会导致这种问题呢?对于32位操作系统来说,单次次操作能处理的最长长度为32bit,而long类型8字节64bit,所以对long的读写都要两条指令才能完成(即每次读写64bit中的32bit)。如果JVM要保证long和double读写的原子性,势必要做额外的处理。那么,JVM有对这一情况进行额外处理吗?针对这一问题可以参考Java语言规范文档:jls-17 Non-Atomic Treatment of double and longFor the purposes of the Java programming language memory model, a single write to a non-volatile long or double value is treated as two separate writes: one to each 32-bit half. This can result in a situation where a thread sees the first 32 bits of a 64-bit value from one write, and the second 32 bits from another write.Writes and reads of volatile long and double values are always atomic.Writes to and reads of references are always atomic, regardless of whether they are implemented as 32-bit or 64-bit values.Some implementations may find it convenient to divide a single write action on a 64-bit long or double value into two write actions on adjacent 32-bit values. For efficiency’s sake, this behavior is implementation-specific; an implementation of the Java Virtual Machine is free to perform writes to long and double values atomically or in two parts.Implementations of the Java Virtual Machine are encouraged to avoid splitting 64-bit values where possible. Programmers are encouraged to declare shared 64-bit values as volatile or synchronize their programs correctly to avoid possible complications.从规定中我们可以知道:对于64位的long和double,如果没有被volatile修饰,那么对其操作可以不是原子的。在操作的时候,可以分成两步,每次对32位操作。如果使用volatile修饰long和double,那么其读写都是原子操作对于64位的引用地址的读写,都是原子操作在实现JVM时,可以自由选择是否把读写long和double作为原子操作推荐JVM实现为原子操作从程序得到的结果来看,32位的HotSpot没有把long和double的读写实现为原子操作。在读写的时候,分成两次操作,每次读写32位。因为采用了这种策略,所以64位的long和double的读与写都不是原子操作。
|