zoukankan      html  css  js  c++  java
  • java中的volatile变量

    同步与线程间通信:

      • 通信 
        通信是指消息在两条线程之间传递。 
        既然要传递消息,那接收线程 和 发送线程之间必须要有个先后关系,此时就需要用到同步。通信和同步是相辅相成的。

      • 同步 
        同步是指,控制多条线程之间的执行次序。

    线程间通信方式:

    • 共享内存 
      共享内存指的是多条线程共享同一片内存,发送者将消息写入内存,接收者从内存中读取消息,从而实现了消息的传递。 
      但这种方式有个弊端,即需要程序员来控制线程的同步,即线程的执行次序。

    这种方式并没有真正地实现消息传递,只是从结果上来看就像是将消息从一条线程传递到了另一条线程。

      • 消息传递 
        顾名思义,消息传递指的是发送线程直接将消息传递给接收线程。 
        由于执行次序由并发机制完成,因此不需要程序员添加额外的同步机制,但需要声明消息发送和接收的代码。

     

    java多线程内存模型:

    所有线程都共享一片内存,用于存储共享变量; 
    此外,每条线程都有各自的存储空间,存储各自的局部变量、方法参数、异常对象。

    volatile的使用:

    public volatile boolean flag;

    1)volatile在重排序(编译器、处理器在不改变程序执行结果的前提下,重新排列指令的执行顺序,以达到最佳的运行效率)中的使用: 

    在以下情况下,即使两行代码之间没有依赖关系,也不会发生重排序:

    • volatile读

      • 若volatile读操作的前一行为volatile读/写,则这两行不会发生重排序
      • volatile读操作和它后一行代码都不会发生重排序
    • volatile写

      • volatile写操作和它前一行代码都不会发生重排序;
      • 若volatile写操作的后一行代码为volatile读/写,则这两行不会发生重排序。

    volatile保证共享变量的内存可见性:

    volatile修饰了一个成员变量后,这个变量的读写就会比普通变量多一些步骤。

    • volatile变量写 
      当被volatile修饰的变量进行写操作时,这个变量将会被直接写入共享内存,而非线程的专属存储空间。

    • volatile变量读 
      当读取一个被volatile修饰的变量时,会直接从共享内存中读,而非线程专属的存储空间中读。

    通过对volatile变量读写的限制,就能保证线程每次读到的都是最新的值,从而确保了该变量的内存可见性。

    volatile变量只能确保long、double读写的"原子性"(volatile在其他情况下是不能保证原子性的):

    在Java中的所有类型中,有long、double类型比较特殊,他们占据8字节(64比特),其余类型都小于64比特。在32位操作系统中,CPU一次只能读取/写入32位的数据,因此对于64位的long、double变量的读写会进行两步。在多线程中,若一条线程只写入了long型变量的前32位,紧接着另一条线程读取了这个只有“一半”的变量,从而就读到了一个错误的数据。 
    为了避免这种情况,需要在用volatile修饰long、double型变量.

    其实如果一个变量加了volatile关键字,就会告诉编译器和JVM的内存模型:这个变量是对所有线程共享的、可见的,每次jvm都会读取最新写入的值并使其最新值在所有CPU可见。所以说的是线程可见性,没有提原子性。

    下面我们用一个例子说明volatile没有原子性,不要将volatile用在getAndOperate场合(这种场合不原子,需要再加锁,如i++),仅仅set或者get的场景是适合volatile的。
    例如你让一个volatile的integer自增(i++),其实要分成3步:1)读取volatile变量值到local; 2)增加变量的值;3)把local的值写回,让其它的线程可见。这3步的jvm指令为:

    mov    0xc(%r10),%r8d ; Load
    inc    %r8d           ; Increment
    mov    %r8d,0xc(%r10) ; Store
    lock addl $0x0,(%rsp) ; StoreLoad Barrier

    注意最后一步是内存屏障。
    什么是内存屏障(Memory Barrier)?
    内存屏障(memory barrier)是一个CPU指令。基本上,它是这样一条指令: a) 确保一些特定操作执行的顺序; b) 影响一些数据的可见性(可能是某些指令执行后的结果)。编译器和CPU可以在保证输出结果一样的情况下对指令重排序,使性能得到优化。插入一个内存屏障,相当于告诉CPU和编译器先于这个命令的必须先执行,后于这个命令的必须后执行。内存屏障另一个作用是强制更新一次不同CPU的缓存。例如,一个写屏障会把这个屏障前写入的数据刷新到缓存,这样任何试图读取该数据的线程将得到最新值,而不用考虑到底是被哪个cpu核心或者哪颗CPU执行的。

    内存屏障(memory barrier)和volatile什么关系?上面的虚拟机指令里面有提到,如果你的字段是volatile,Java内存模型将在写操作后插入一个写屏障指令,在读操作前插入一个读屏障指令。这意味着如果你对一个volatile字段进行写操作,你必须知道:1、一旦你完成写入,任何访问这个字段的线程将会得到最新的值。2、在你写入前,会保证所有之前发生的事已经发生,并且任何更新过的数据值也是可见的,因为内存屏障会把之前的写入值都刷新到缓存。

    为什么volatile变量用不同的线程访问修改后访问的结果会不一样(多数情况下不建议使用volatile变量提供可见性):

    JVM在运行时内存分配汇总有一个内存区域称为虚拟机栈, 线程栈保存了线程运行时的信息,当线程访问某个对象的值的时候,首先通过对象的引用找到对应在堆内存的变量的值,然后把堆内存变量的值load到本地内存中(当前线程所分配内存区域),建立一个变量副本, 之后线程不再和对象在堆内存的变量有任何联系,而是直接修改副本的值,在修改完成之后自动把变量副本写回堆内存,这样堆内存的值就会改变:

    read and load: 从主内存复制变量到当前工作内存

    use and assign: 执行代码, 改变共享变量

    store and write: 用工作内存数据刷新主内存相关内容

    但在read and load 之后, 如果线程1对该volatile变量修改还未结束, 线程2也进行修改, 但修改的是最初的值, 将会导致并发的发生.

    volatile变量使用的场景:

    1).对变量的写入不依赖变量当前的值, 或者能确保只有单线程更新变量的值

    2).该变量不会与其他状态变量一起纳入不变性条件中

    3).在访问变量时不需要加锁

  • 相关阅读:
    动态生成 Excel 文件供浏览器下载的注意事项
    JavaEE 中无用技术之 JNDI
    CSDN 泄露用户密码给我们什么启示
    刚发布新的 web 单点登录系统,欢迎下载试用,欢迎提建议
    jQuery jqgrid 对含特殊字符 json 数据的 Java 处理方法
    一个 SQL 同时验证帐号是否存在、密码是否正确
    PostgreSQL 数据库在 Windows Server 2008 上安装注意事项
    快速点评 Spring Struts Hibernate
    Apache NIO 框架 Mina 使用中出现 too many open files 问题的解决办法
    解决 jQuery 版本升级过程中出现 toLowerCase 错误 更改 doctype
  • 原文地址:https://www.cnblogs.com/kexianting/p/8504236.html
Copyright © 2011-2022 走看看