并发5️⃣内存②volatile 原理、happens-before 规则

1、volatile 原理(❗)

volatile 可以保证可见性、有序性

  • 原理内存屏障(Memory Barrier / Memory Fence)
  • 应用单例模式-双重检查锁(double-checked locking, DCL)

1.1、内存屏障

volatile 修饰的变量决定了内存屏障的位置。

屏障前后的任意共享变量都会根据规则生效。

写屏障(sfence) 读屏障(lfence)
位置 在 volatile 变量的写指令之后 在 volatile 变量的读指令之前
可见性保证 写屏障之前对任意共享变量的改动,都会同步到主存中 读屏障之后对任意共享变量的读取,是主存中的最新值
有序性保证 写屏障之前的代码不会被重排序到写屏障中 读屏障之后的代码不会被重排序到读屏障之前

1.2、DCL

1.2.1、实现

public class DclSingleton {
    private static volatile DclSingleton instance;

    private DclSingleton() {}

    public DclSingleton getInstance() {
        // 第一次检查
        if (instance == null) {
            synchronized (DclSingleton.class) {
                // 第二次检查
                if (instance == null) {
                    instance = new DclSingleton();
                }
            }
        }
        return instance;
    }
}

1.2.2、并发度分析

初次访问 getInstance()

  • 单线程访问 getInstance()
    1. 一次检查通过,加锁。
    2. 二次检查通过,初始化并返回实例对象。
  • 多线程访问 getInstance()
    1. 线程 t1 先进入方法,一次检查通过,加锁
    2. 此时线程 t2 一次检查通过但加锁失败,进入 Monitor 的 EntryList 阻塞。
    3. 线程 t1 二次检查通过,初始化实例对象,返回实例对象之前释放对象锁并唤醒 t2。
    4. 线程 t2 加锁,二次检查不通过,直接返回实例对象。

再次访问 getInstance()

无论是单线程还是多线程,第一次检查不通过,说明对象已实例化,直接返回实例对象。

1.2.3、volatile 作用

分析getInstance() 中的对象实例化

instance = new DclSingleton();

对应 4 行字节码

  1. new创建对象实例,分配堆内存,将对象引用压入操作数栈。

  2. dup:复制操作数栈的栈顶数据,用于初始化(参考字节码技术

  3. invokespecial初始化

  4. putstatic将对象引用赋值给变量 instance。

    new #2
    dup
    invokespecial #3
    putstatic #4
    

指令重排:假设以上代码没有 volatile

JVM 运行期优化,可能将 invokespecialputstatic 顺序对调。

  1. 首次调用 getInstance(),执行 putstatic
    • 对象尚未初始化
    • 此时 instance 被赋值成一个未初始化的对象非空)。
  2. 再次调用 getInstance()
    • 一次检查不通过,直接返回 instance
    • 但此时的 instance 尚未初始化完毕(❗)

2、happens-before

happens-before:可见性有序性的规则总结。

规定了对共享变量的写操作对其它线程的读操作可见。

写之后谁可见?
t1 对 lock 加锁期间的写 对 lock 加锁的线程
t1 对 volatile 变量 x 的写 读取 x 的线程
t1 启动(start)前的写 t1
t1 终止(死亡)前的写 得知 t1 终止的线程
t1.isAlive()t1.join()
t1 被中断(interupt)前的写 得知 t1 被中断的线程
t1.interrupted()t1.isInterrupted()

注:传递性

  • 若 t2 可见 t1 的写操作,t3 可知 t2 的写操作,则 t3 可知 t1 的写操作。
  • t1 → t2t2 → t3 👉 t1 → t3
posted @ 2022-04-25 14:49  Jaywee  阅读(50)  评论(0编辑  收藏  举报

👇