• volatile


    上述代码可能会有2个问题,1、内存可见性。 2、指令重排序

    什么是内存可见性

     Java 内存模型

     JMM 定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存中存储了该线程以读/写共享变量的副本。

    所有的共享变量都存储于主内存。这里所说的变量指的是实例变量和类变量,不包含局部变量,因为局部变量是线程私有的,因此不存在竞争问题。

    • 每一个线程还存在自己的工作内存,线程的工作内存,保留了被线程使用的变量的工作副本。
    • 线程对变量的所有的操作(读,取)都必须在工作内存中完成,而不能直接读写主内存中的变量。
    • 不同线程之间也不能直接访问对方工作内存中的变量,线程间变量的值的传递需要通过主内存中转来完成。

    这样当线程三修改A的共享变量时,并没有及时的更新到主内存中,线程一再读取的时候拿到的就是没有更新过的变量A

    我们如何保证多线程下共享变量的可见性呢?也就是当一个线程修改了某个值后,对其他线程是可见的。

    1、加锁

    2、使用volatile关键字。

    这里我们使用了加锁,所以不会有内存可见性问题。volatile 保证了不同线程对共享变量操作的可见性,也就是说一个线程修改了 volatile 修饰的变量,当修改后的变量写回主内存时,其他线程能立即看到最新值。

    那么这里我们为什么不使用volatile呢,毕竟使用volatile成本更低,这里就涉及到了volatile的原子性问题。它不能像synchronized能够保证操作的原子性。再多线程环境下,使用 volatile 修饰的变量是线程不安全的。

    什么是指令重排序

     上述的doucleCheck = new DoucleCheck();代码有问题:

    其底层会分为三个操作:

    1. 分配一块内存。

    2. 在内存上初始化成员变量。

    3. 把doucleCheck 引用指向内存。

    在这三个操作中,操作2和操作3可能重排序,即先把doucleCheck 指向内存,再初始化成员变量,因为 二者并没有先后的依赖关系。此时,另外一个线程可能拿到一个未完全初始化的对象。这时,直接访问 里面的成员变量,就可能出错。这就是典型的“构造方法溢出”问题。

    解决办法也很简单,就是为doucleCheck 变量加上volatile修饰。 volatile的三重功效:64位写入的原子性、内存可见性和禁止重排序。

    volatile为什么可以禁止指令重排序呢

    这里就涉及到jmm的happen-before规则

      JMM可以通过happens-before关系向程序员提供跨线程的内存可见性保证(如果A线程的写操作a与B线程的读操作b之间存在happens-before关系,尽管a操作和b操作在不同的线程中执行,但JMM向程序员保证a操作将对b操作可见)。

    1)如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。

    两个操作之间存在happens-before关系,并不意味着Java平台的具体实现必须要按照happens-before关系指定的顺序来执行。如果重排序之后的执行结果,与按happens-before关系来执行的结果一致,那么这种重排序并不非法(也就是说,JMM允许这种重排序)。
    如果A happen-before B,意味着A的执行结果必须对B可见,也就是保证跨线程的内存可见性。A happen before B不代表A一定在B之前执行。因为,对于多线程程序而言,两个操作的执行顺序是不确 定的。happen-before只确保如果A在B之前执行,则A的执行结果必须对B可见。定义了内存可见性的约 束,也就定义了一系列重排序的约束。

    所以上述代码改为

    4.3.3 happen-before规则总结 1. 单线程中的每个操作,happen-before于该线程中任意后续操作。 2. 对volatile变量的写,happen-before于后续对这个变量的读。 3. 对synchronized的解锁,happen-before于后续对这个锁的加锁。 4. 对final变量的写,happen-before于final域对象的读,happen-before于后续对final变量的 读。 四个基本规则再加上happen-before的传递性,就构成JMM对开发者的整个承诺。在这个承诺以外 的部分,程序都可能被重排序,都需要开发者小心地处理内存可见性问题。

    happen-before规则总结

    1. 单线程中的每个操作,happen-before于该线程中任意后续操作。

    2. 对volatile变量的写,happen-before于后续对这个变量的读。

    3. 对synchronized的解锁,happen-before于后续对这个锁的加锁。

    4. 对final变量的写,happen-before于final域对象的读,happen-before于后续对final变量的 读。

    四个基本规则再加上happen-before的传递性,就构成JMM对开发者的整个承诺。在这个承诺以外 的部分,程序都可能被重排序,都需要开发者小心地处理内存可见性问题。

  • 相关阅读:
    char/byte/short类型的加法和类型转换问题
    Java四种基本数据类型
    Git知识集锦
    解决给自己的博客添加百度统计不能验证的问题
    C++静态代码分析工具推荐——PVS-Studio
    Qt在控件未显示时如何获取正确的控件尺寸
    C#程序如何捕捉未try/catch的异常——不弹“XXX已停止工作”报错框
    win10下vs2015编译的程序如何运行在win7等系统(无需安装Redistributable)
    Qt分页导航控件
    win server 2008配置ftp无法登陆问题的解决办法
  • 原文地址:https://www.cnblogs.com/fuqiang-zhou/p/15054871.html
Copyright © 2020-2023  润新知