Java 中 long 和 double 的原子性?

  • JVM中对long的操做是否是原子操做?java

  • 为何对long的操做不是原子的?less

  • 在硬件,操做系统,JVM都是64位的状况下呢?jvm


java中基本类型中,long和double的长度都是8个字节,32位(4字节)处理器对其读写操做没法一次完成,那么,JVM,long和double是原子性的吗?ide

JVM中对long的操做是否是原子操做?

首先,经过一段程序对long的原子性进行判断。测试程序以下:测试

public class LongAtomTest implements Runnable {

    private static long field = 0;

    private volatile long value;

    public long getValue() {
        return value;
    }

    public void setValue(long value) {
        this.value = value;
    }

    public LongAtomTest(long value) {
        this.setValue(value);
    }

    @Override
    public void run() {
        int i = 0;
        while (i < 100000) {
            LongAtomTest.field = this.getValue();
            i++;
            long temp = LongAtomTest.field;
            if (temp != 1L && temp != -1L) {
                System.out.println("出现错误结果" + temp);
                System.exit(0);
            }
        }
        System.out.println("运行正确");
    }

    public static void main(String[] args) throws InterruptedException {
        // 获取并打印当前JVM是32位仍是64位的
        String arch = System.getProperty("sun.arch.data.model");
        System.out.println(arch+"-bit");
        LongAtomTest t1 = new LongAtomTest(1);
        LongAtomTest t2 = new LongAtomTest(-1);
        Thread T1 = new Thread(t1);
        Thread T2 = new Thread(t2);
        T1.start();
        T2.start();
        T1.join();
        T2.join();
    }

}

能够看到,程序中有两条线程t1,t2; t1,t2各自不停的给long类型的静态变量field赋值为1,-1; t1,t2每次赋值后,会读取field的值,若field值既不是1又不是-1,就将field的值打印出来ui

若是对long的写入和读取操做是原子性的,那么,field的值只多是1或者-1this

运行结果以下atom

32-bit
出现错误结果-4294967295
运行正确

能够看出,当线程t1,t2同时对long进行写的时候,long出现了既不是t1写入的值,又不是t2写入的值。能够推测,jvm中对long的操做并不是原子操做。spa

为何对long的操做不是原子的?

JVM内存模型中定义了8中原子操做:操作系统

  1. lock:将一个变量标识为被一个线程独占状态

  2. unclock:将一个变量从独占状态释放出来,释放后的变量才能够被其余线程锁定

  3. read:将一个变量的值从主内存传输到工做内存中,以便随后的load操做

  4. load:把read操做从主内存中获得的变量值放入工做内存的变量的副本中

  5. use:把工做内存中的一个变量的值传给执行引擎,每当虚拟机遇到一个使用到变量的指令时都会使用该指令

  6. assign:把一个从执行引擎接收到的值赋给工做内存中的变量,每当虚拟机遇到一个给变量赋值的指令时,都要使用该操做

  7. store:把工做内存中的一个变量的值传递给主内存,以便随后的write操做

  8. 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 long

For 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.

从规定中咱们能够知道

  1. 对于64位的long和double,若是没有被volatile修饰,那么对其操做能够不是原子的。在操做的时候,能够分红两步,每次对32位操做。

  2. 若是使用volatile修饰long和double,那么其读写都是原子操做

  3. 对于64位的引用地址的读写,都是原子操做

  4. 在实现JVM时,能够自由选择是否把读写long和double做为原子操做

  5. 推荐JVM实现为原子操做

从程序获得的结果来看,32位的HotSpot没有把long和double的读写实现为原子操做。 在读写的时候,分红两次操做,每次读写32位。由于采用了这种策略,因此64位的long和double的读与写都不是原子操做。

在硬件,操做系统,JVM都是64位的状况下呢?

对于64bit的环境来讲,单次操做能够操做64bit的数据,便可以以一次性读写long或double的整个64bit。所以咱们能够猜想,在64位的环境下,long和double的读写有多是原子操做。 在换了64位的JVM以后,屡次运行,结果都是正确的

64-bit
运行正确
运行正确

结果代表,在64bit的虚拟机下,long的处理是原子性的

 

 

https://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&mid=2247486080&idx=2&sn=1fc60b6bf77308e17ab8cb731f33f99f&chksm=fa497531cd3efc27fd2479e4ffd6ba32d5900eae0c3aa565662a0d9a38667b403040aec811fc&mpshare=1&scene=1&srcid=0112sC5LAE1CqRDEp8sAolHv&key=0061820b7e211c2777061392c3d0fd794715b1defc7150ddc2f963aa9df561ca968ea5aa8ab89c6b1beaf5d33915386824e2aa2391c8178e005ee9d18c0fbf62b884a86c53bf1bd181d928af7f14c8f9&ascene=0&uin=MTA2NzUxMDAyNQ%3D%3D&devicetype=iMac+MacBookAir6%2C2+OSX+OSX+10.10.5+build(14F2511)&version=11020012&lang=zh_CN&pass_ticket=sv4l%2BGEe3jDMSGyGy8HbXOjLuiqF00ftuchHsiP4ANQLuOaLo%2FLBiGBEJRndwZZ5

 

 

模拟模型:

 

线程1             线程2

写前32

                       读前32+后32(这个地方出现读取异常)

写后32