详细谈谈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的处理是原子性的。

总结

到此这篇关于Java中long和double原子性的文章就介绍到这了,更多相关Java中long和double原子性内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 为什么Java volatile++不是原子性的详解

    问题 在讨论原子性操作时,我们经常会听到一个说法:任意单个volatile变量的读写具有原子性,但是volatile++这种操作除外. 所以问题就是:为什么volatile++不是原子性的? 答案 因为它实际上是三个操作组成的一个符合操作. 首先获取volatile变量的值 将该变量的值加1 将该volatile变量的值写会到对应的主存地址 一个很简单的例子: 如果两个线程在volatile读阶段都拿到的是a=1,那么后续在线程对应的CPU核心上进行自增当然都得到的是a=2,最后两个写操作不管怎

  • java 并发中的原子性与可视性实例详解

    java 并发中的原子性与可视性实例详解 并发其实是一种解耦合的策略,它帮助我们把做什么(目标)和什么时候做(时机)分开.这样做可以明显改进应用程序的吞吐量(获得更多的CPU调度时间)和结构(程序有多个部分在协同工作).做过java Web开发的人都知道,Java Web中的Servlet程序在Servlet容器的支持下采用单实例多线程的工作模式,Servlet容器为你处理了并发问题. 原子性 原子是世界上的最小单位,具有不可分割性.比如 a=0:(a非long和double类型) 这个操作是不

  • 详细谈谈Java中long和double的原子性

    目录 前言 JVM中对long的操作是不是原子操作? 为什么对long的操作不是原子的? 在硬件,操作系统,JVM都是64位的情况下呢? 总结 前言 java中基本类型中,long和double的长度都是8个字节,32位(4字节)处理器对其读写操作无法一次完成,那么,JVM,long和double是原子性的吗? JVM中对long的操作是不是原子操作? 首先,通过一段程序对long的原子性进行判断.测试程序如下: public class LongAtomTest implements Runn

  • 谈谈Java中的守护线程与普通线程

    守护线程与普通线程的唯一区别是:当JVM中所有的线程都是守护线程的时候,JVM就可以退出了:如果还有一个或以上的非守护线程则不会退出.(以上是针对正常退出,调用System.exit则必定会退出) 所以setDeamon(true)的唯一意义就是告诉JVM不需要等待它退出,让JVM喜欢什么退出就退出吧,不用管它. 守护线程在没有用户线程可服务时自动离开,在Java中比较特殊的线程是被称为守护(Daemon)线程的低级别线程.这个线程具有最低的优先级,用于为系统中的其它对象和线程提供服务.将一个用

  • 谈谈Java中对象,类和this,super,static关键字的使用

    目录 Java对象究竟是什么 创建对象的过程 创建多个对象时,内存的变化 无处不在的this和super关键字 static关键字 为何如此特殊 Java对象究竟是什么 对象:对象是类的一个实例,有状态和行为. 类:类是一个模板,它描述一类对象的行为和状态.例如 人 是一个类 其状态有:姓名.性别.身高.体重等 其行为:吃饭.睡觉.聊天.运动等     public class Person {         /**          * 状态 or 属性          */       

  • 简单谈谈Java中的方法和方法重载

    今天我们就讲一点内容,来说说Java中的方法和方法重载以及需要注意的一些地方: 方法: Java的方法类似与其他语言的函数,是一段用来完成特定功能的代码片段, 声明格式: [修饰符1 修饰符2 ....] ,返回值类型 方法名 (形式参数列表) { Java语句: - - -} 形式参数:在方法被调用时用于接受外界输入的数据: 实参: 调用方法时世界传给方法的数据: 返回值: 方法在执行完毕后返回给调用他的环境的数据: 返回值类型: 事先约定好的返回值的数据类型,如无返回值必须给出返回值类型vo

  • 详解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中自定义注解及使用场景

    Java自定义注解一般使用场景为:自定义注解+拦截器或者AOP,使用自定义注解来自己设计框架,使得代码看起来非常优雅.本文将先从自定义注解的基础概念说起,然后开始实战,写小段代码实现自定义注解+拦截器,自定义注解+AOP. 一. 什么是注解(Annotation) Java注解是什么,以下是引用自维基百科的内容 Java注解又称Java标注,是JDK5.0版本开始支持加入源代码的特殊语法元数据. Java语言中的类.方法.变量.参数和包等都可以被标注.和Javadoc不同,Java标注可以通过反

  • 详细总结Java中常用的原子类

    一.什么是原子类 Java中提供了一些原子类,原子类包装了一个变量,并且提供了一系列对变量进行原子性操作的方法.我们在多线程环境下对这些原子类进行操作时,不需要加锁,大大简化了并发编程的开发. 二.原子类的底层实现 目前Java中提供的原子类大部分底层使用了CAS锁(CompareAndSet自旋锁),如AtomicInteger.AtomicLong等:也有使用了分段锁+CAS锁的原子类,如LongAdder等. 三.常用的原子类 3.1 AtomicInteger与AtomicLong At

  • 详细谈谈MYSQL中的COLLATE是什么

    前言 在mysql中执行show create table <tablename>指令,可以看到一张表的建表语句,example如下: CREATE TABLE `table1` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `field1` text COLLATE utf8_unicode_ci NOT NULL COMMENT '字段1', `field2` varchar(128) COLLATE utf8_unicode_ci

  • 详细介绍Java中的各种锁

    一.一张图了解21种锁 二.乐观锁 应用 CAS 思想 一种乐观思想,假定当前环境是读多写少,遇到并发写的概率比较低,读数据时认为别的线程不会正在进行修改 实现 写数据时,判断当前 与期望值是否相同,如果相同则进行更新(更新期间加锁,保证是原子性的) 三.悲观锁 应用 synchronized.vector.hashtable 思想: 一种悲观思想 ** ,即认为写多读少,遇到并发写的可能性高 实现 每次读写数据都会认为其他线程会修改,所以每次读写数据时都会上锁 缺点 他线程想要读写这个数据时,

  • java中int、double、char等变量的取值范围详析

    目录 1.java工具 2.代码简单框架 3.int 4.long 5.double 6.float 7.char 8.byte 9.short 10.总结 1.java工具 1.JDK:Java开发者工具,建议安装1.8版的. 2.IEDA:集成开发环境 2.代码简单框架 public为访问修饰限定符 class为类 HelloWorld为类的名称 public static void main(String[] args)相当于C语言的的main函数 System.out.println()

随机推荐