Java虚拟机规范定义的许多规则中的一条:所有对基本类型的操作除了某些对long类型和double类型的操作之外,都是原子级的;

当线程把主存中的 long/double类型的值读到线程内存中时,可能是两次32位值的写操作,显而易见,如果几个线程同时操作,那么就可能会出现高低2个32位值出错的情况发生。即long,double高低位问题,非线程安全

举例说明:

即如有一个long类型的field字段,某个线程正在执行 field = 123L ,而同时有另一个线程正在执行 field = 456L,这样的指定操作之后field的值会是什么,是无法保证的。也许是123L,也可能是456L,或许是0L,甚至还可能是31415926L

JVM内存模型中定义了8种原子操作

  1. lock:将一个变量标识为被一个线程独占状态
  2. unclock:将一个变量从独占状态释放出来,释放后的变量才可以被其他线程锁定
  3. read:将一个变量的值从主内存传输到工作内存中,以便随后的load操作
  4. load:把read操作从主内存中得到的变量值放入工作内存的变量的副本中
  5. use:把工作内存中的一个变量的值传给执行引擎,每当虚拟机遇到一个使用到变量的指令时都会使用该指令
  6. assign:把一个从执行引擎接收到的值赋给工作内存中的变量,每当虚拟机遇到一个给变量赋值的指令时,都要使用该操作
  7. store:把工作内存中的一个变量的值传递给主内存,以便随后的write操作
  8. write:把store操作从工作内存中得到的变量的值写到主内存中的变量

对于32位操作系统来说单次次操作能处理的最长长度为32bit,而long类型8字节64bit,所以对long的读写都要两条指令才能完成(即每次读写64bit中的32bit);如果JVM要保证long和double读写的原子性,势必要做额外的处理

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();
    }

}

以上代码在32位JVM上和64位JVM上运行结果将不一致

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

结论

  • 对于64位的long和double,如果没有被volatile修饰,那么对其操作可以不是原子的。在操作的时候,可以分成两步,每次对32位操作;
  • 如果使用volatile修饰long和double,那么其读写都是原子操作;
  • 在实现JVM时,可以自由选择是否把读写long和double作为原子操作;
  • java中对于long和double类型的写操作不是原子操作,而是分成了两个32位的写操作。读操作是否也分成了两个32位的读呢?在JSR-133之前的规范中,读也是分成了两个32位的读,但是从JSR-133规范开始,即JDK5开始,读操作也都具有原子性;
  • java中对于其他类型的读写操作都是原子操作(除long和double类型以外);
  • 对于引用类型的读写操作都是原子操作,无论引用类型的实际类型是32位的值还是64位的值;

  • 对于long类型的不恰当操作可能读取到从未出现过的值。而对于int的不恰当操作则可能读取到旧的值;

results matching ""

    No results matching ""