Java 中 long 和 double 的原子性?

  • JVM中对long的操作是不是原子操作?
  • 为什么对long的操作不是原子的?
  • 在硬件,操作系统,JVM都是64位的情况下呢?

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

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的值打印出来

如果对long的写入和读取操作是原子性的,那么,field的值只可能是1或者-1

运行结果如下

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

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

为什么对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

原文地址:https://www.cnblogs.com/silyvin/p/10424717.html

时间: 2024-10-11 09:24:07

Java 中 long 和 double 的原子性?的相关文章

Java中String转换Double类型 Java小数点后留两位

Java中String转换Double类型 double num1 = 0.0; String qq = "19.987"; num1 = Double.valueOf(qq.toString()); 太祖元年 Java小数点后留两位 double num1 =11; double num1 =21; String num1 =""; DecimalFormat df = new DecimalFormat(".00");num3 = df.fo

精确计算java中float和double的精度

[本文相关的代码放在github上,地址为:https://github.com/VigourJiang/StructuredFloat] Java中double类型的格式是遵循IEEE 754标准的.尽管数学意义上的小数是连续的,但double仅仅能表示其中的一些离散点,把这些离散点组成的集合记为S,S的大小还是有限的.如果要保存的小数P刚好在集合S内,那么double类型就能精确的表示P:否则double类型只能从集合S中找一个与P最近的离散点P'代替P. 以上表述对于float也成立.IE

Java中的浮点型(Double&amp;Float)计算问题

在刚刚做完的一个项目中,遇到了double型计算不精确的问题.到网上查找后,问题得到解决.经验共享,在这里总结一下. Java中的浮点数类型float和double不能够进行精确运算.这个问题有时候非常严重.比如,经过double型直接计算,1.4×1.5有时会得出2.0999999999999996的结果,但实际上,应该得到2.10.而且,类似的情况并不仅限于乘法计算. 在有的编程语言中提供了专门的货币类型来处理这种情况,但是Java没有.所以,在商业计算中我们要用:java.math.Big

Java中float和double转换的问题

为什么double转float不会出现数据误差,而float转double却误差如此之大?   double d = 3.14; float f = (float)d; System.out.println(f); 输出结果是:3.14; float f = 127.1f; double d = f; System.out.println(d); 输出结果是:127.0999984741211 为什么结果会是这样呢? 如何避免这样的问题发生,让float转double能得到实际的数据? 解决办法

Java中如何解决double和float精度不准的问题

我们知道浮点数是无法在计算机中准确表示的,例如0.1在计算机中只是表示成了一个近似值,因此,对付点数的运算时结果具有不可预知性. 在进行数字运算时,如果有double或float类型的浮点数参与计算,偶尔会出现计算不准确的情况.如以下示例代码: package ex; public class BigDeciTest { public static void main(String[] args){ System.out.println(0.05+0.01); System.out.printl

Java中求List&lt;double[]&gt;中的最大值和最小值

如需转载请标明出处:http://blog.csdn.net/itas109 QQ技术交流群:129518033 Reference:http://stackoverflow.com/questions/8093163/min-max-values-of-a-listdouble 现在有一个如下的数组,求其最大值和最小值 List<double[]> values = new ArrayList<double[]>(); values.add(new double[] { 12.3

JAVA中float与double的区

float是单精度类型,精度是8位有效数字,取值范围是10的-38次方到10的38次方,float占用4个字节的存储空间 double是双精度类型,精度是17位有效数字,取值范围是10的-308次方到10的308次方,double占用8个字节的存储空间 当你不声明的时候,默认小数都用double来表示,所以如果要用float的话,则应该在其后加上f 例如:float a=1.3; 则会提示不能将double转化成float  这成为窄型转化 如果要用float来修饰的话,则应该使用float a

Java中的volatile关键字为什么不是不具有原子性

Java中long赋值不是原子操作,因为先写32位,再写后32位,分两步操作,而AtomicLong赋值是原子操作,为什么?为什么volatile能替代简单的锁,却不能保证原子性?这里面涉及volatile,是java中的一个我觉得这个词在Java规范中从未被解释清楚的神奇关键词,在Sun的JDK官方文档是这样形容volatile的: The Java programming language provides a second mechanism, volatile fields, that

Java中的简单浮点数类型float和double不能够进行精确运算

在java中,简单的浮点类型float和double是不能够进行运算.我们先看下面的两个程序代码: 代码一: import java.util.Scanner; class Circle { double radius; static final double PI=3.14; public Circle(){this.radius=0;} public Circle(double r){this.radius=r;} public double getArea(){return PI*this