【多线程学习十四】详谈volatile原理

volatile原理

底层是内存屏障

  • 对volatile变量的写指令后会加入写屏障
  • 对voliatile变量的读指令前会加入读屏障

保证可见性

写屏障保证在该屏障之前的对共享变量的改动都同步到主存中

public void actor2(I_Result r) {
 	num = 2;
 	ready = true; // ready 是 volatile 赋值带写屏障
 	// 写屏障
}

读屏障保证在该屏障之后对共享变量的读取都是加载主存中最新数据

public void actor1(I_Result r) {
     // 读屏障
     // ready 是 volatile 读取值带读屏障
     if(ready) {
     r.r1 = num + num;
     } else {
     r.r1 = 1;
     }
}

在这里插入图片描述

保证有序性

写屏障会确保指令重排序时,不会将写屏障之前的代码排在写屏障之后

public void actor2(I_Result r) {
     num = 2;
     ready = true; // ready 是 volatile 赋值带写屏障
     // 写屏障
}

读屏障会确保指令重排序时,不会将读屏障之后的代码排在读屏障之前

public void actor1(I_Result r) {
     // 读屏障
     // ready 是 volatile 读取值带读屏障
     if(ready) {
     r.r1 = num + num;
     } else {
     r.r1 = 1;
     }
}

但是读写屏障不能解决指令交错

  • 写屏障仅仅是保证之后的读能够读取最新的结果,但不能保证读在写之前
  • 有序性的保证只是保证了本线程内相关代码不被重排序
    在这里插入图片描述

double-checked locking问题

public final class Singleton {
    private Singleton() { }
    private static Singleton INSTANCE = null;
    public static Singleton getInstance() { 
        if(INSTANCE == null) { // t2
            // 首次访问会同步,而之后的使用没有 synchronized
            synchronized(Singleton.class) {
                if (INSTANCE == null) { // t1
                    INSTANCE = new Singleton(); 
                } 
            }
        }
        return INSTANCE;
}
}

特点

  • 懒惰实例化

  • 首次使用getInstance()才使用了synchronized加锁,后续使用时无需加锁

  • 第一个if在同步块之外,使用了INSTANCE变量
    在这里插入图片描述

关键在于 0: getstatic 这行代码在 monitor 控制之外,它就像之前举例中不守规则的人,可以越过 monitor 读取INSTANCE 变量的值

这时 t1 还未完全将构造方法执行完毕,如果在构造方法中要执行很多初始化操作,那么 t2 拿到的是将是一个未初始化完毕的单例

对 INSTANCE 使用 volatile 修饰即可,可以禁用指令重排,但要注意在 JDK 5 以上的版本的 volatile 才会真正有效 .

public final class Singleton {
    private Singleton() { }
    private static volatile Singleton INSTANCE = null;
    
    public static Singleton getInstance() {
        // 实例没创建,才会进入内部的 synchronized代码块
        if (INSTANCE == null) { 
            synchronized (Singleton.class) { // t2
                // 也许有其它线程已经创建实例,所以再判断一次
                if (INSTANCE == null) { // t1
                    INSTANCE = new Singleton();
                }
            }
        }
        return INSTANCE;
    }
}

读写 volatile 变量时会加入内存屏障(Memory Barrier(Memory Fence)),保证下面两点:

  • 可见性

    • 写屏障(sfence)保证在该屏障之前的 t1 对共享变量的改动,都同步到主存当中
    • 读屏障(lfence)保证在该屏障之后 t2 对共享变量的读取,加载的是主存中最新数据
  • 有序性

    • 写屏障会确保指令重排序时,不会将写屏障之前的代码排在写屏障之后
    • 读屏障会确保指令重排序时,不会将读屏障之后的代码排在读屏障之前
    • 更底层是读写变量时使用 lock 指令来多核 CPU 之间的可见性与有序性

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值