美文网首页我爱编程
Java volatile关键字

Java volatile关键字

作者: _Once1 | 来源:发表于2018-06-11 22:48 被阅读0次
    volatile.png

    内存模型概念

    高速缓存存在的意义:临时变量都存在于内存中,当CPU执行程序时,速度是非常快的,但是牵扯到和内存交互,存取数据(耗时),会影响程序的执行速度,因此,必须引入高速缓存。当执行程序时,会先从内存中读取值,然后复制给高速缓存,之后程序执行,并将结果写入高速缓存,最后,再将缓存中的值刷新进入内存。高速缓存的存在,提高了程序的执行速度

    引入的问题
    多线程程序中,各个线程都有自己的高速缓存,因此当他们读取内存中的同一个值时(即共享变量),无法保证读取进缓存和写入内存时序,即缓存不一致,就会产生错误

    如何解决?

    • 总线加lock
    • 通过缓存一致性协议。其中一个协议的思想为:当一个线程修改了值并将其写入内存之后,会通知其他线程,将其他线程中的缓存置为无效状态,当其他线程需要读取该值时,必须重新从内存中读取

    三个性质

    • 原子性
      指一个或者多个操作,要么全部执行并且执行过程中不被任何因素打断,要么就全部都不执行
    • 可见性
      指当多个线程访问同一个变量时,一旦其中的一个线程改变了该变量的值,其他的线程立刻就可以看到修改后的值
      个人认为可以简单理解为,当一个线程修改了值后,会立即将该值从高速缓存中写入内存,确保其他线程可以正确的读取到该值
    • 有序性
      Java程序执行时,为了提高执行效率,会发生指令重排。
      具体的说,就是处理器不会保证代码会严格按照语句的先后顺序执行,而是会保证其代码的执行结果和严格按照顺序执行语句的结果一致。
      例如:

    Person p = new Person()
    大致会做三件事:

    1. 给Person的实例分配内存空间
    2. 调用Person()的构造函数,初始化成员字段
    3. 将person对象指向分配的内存空间(此时,person就不是null了)

    但是,由于上述乱序的存在,上述过程的2和3顺序是无法保证的
    当在多线程情况下,执行顺序是1-3-2时,当执行完1-3后,切换至线程B,此时得到的p并未完全初始化, 在使用时就会有问题,这也是DLC单例在多线程环境下必须加volatile修饰instance的原因
    注意:要让程序在多线程情况下正确的执行,那就必须同时满足上述三个条件,若其中一个不满足,就可能会导致运行结果不正确


    Java的内存模型

    Java中规定,所有的变量都是存在主存中,每一个线程都有自己的工作内存(相当于高速缓存),在程序执行时,线程对变量的所有操作都必须在其工作内存中

    • 原子性
      Java只会保证所有的基本类型数据,其简单的读取,赋值操作是原子的,变量之间的赋值不是原子的
      若要其他类型数据或者操作实现原子性,可以使用sychronized和lock关键字实现
      volatile变量并不能提供原子性
    • 可见性
      Java提供volatile关键字保证可见性(只针对共享变量而言)
      通过volatile修饰,Java保证对值的修改会立刻刷新至主存中,当其他线程需要读取时(不确定是否一定会从主存中重新读取?),就会去读取新值
      同样的,也可使用sychronized和lock关键字实现
    • 有序性
      可以通过volatile关键字实现部分有序性
      可以使用sychronized和lock关键字实现

    volatile

    如果一个域声明为volatile,那么编译器和虚拟机就知道该域可能是被另一个线程并发更新的
    volatile修饰共享变量的意义:

    1. 保证对该域修改的可见性
    2. 禁止指令进行重排,具体是指:
    • 当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;
    • 在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行
      简单来说,就是volatile会保证volatile之前的语句一定会在volatile之后的语句之前执行,但是并不能保证前面的语句不会乱序执行,其后的语句不会乱序执行

    即是指:

    • 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
    • 它会强制将对缓存的修改操作立即写入主存;
    • 如果是写操作,它会导致其他CPU中对应的缓存行无效

    使用场景:
    由于volatile无法保证原子性,因此,只有在明确可以确保原子性的情况下,可以使用volatile,其效率会高于sychroniezd关键字,例如DCL单例中使用volatile和sychronized确保正确性
    假设对于共享变量,除了赋值操作之外不会再有其他操作,那个可以将其声明为volatile


    参考:这篇博文 https://www.cnblogs.com/dolphin0520/p/3920373.html

    相关文章

      网友评论

        本文标题:Java volatile关键字

        本文链接:https://www.haomeiwen.com/subject/nhckeftx.html