Volatile指令重排详解

一、Volatile不可见性

Volatile关键

 

 二、CPU的乱序执行

CPU在进行读等待的同时执行指令,是CPU乱序的根源,不是乱,而是提高效率 

三、对象的创建过程

 

对象的创建过程:

创建->初始化->建立连接

1.先申请内存,赋值默认值0

2.构造方法赋值初始值,8

3.建立连接,t->T

 

四、DCL单例

4.1 DCL单例代码

DCL单例模式

public class Singleton {
    private volatile static Singleton instance = null;
    public  static Singleton getInstance() {
        if(null == instance) {
            synchronized (Singleton.class) {
                if(null == instance) {
                    instance = new Singleton();
                }
            }
        }

        return instance;

    }
}

4.2 DCL单例(Double Check Lock)到底需不需要volatile

 synchronized本身就可以支持线程可见

 看样子已经达到了要求,除了第一次创建对象之外,其它的访问在第一个if中就返回了,因此不会走到同步块中,已经完美了吗?

  如上代码段中的注释:假设线程一执行到instance = new Singleton()这句,这里看起来是一句话,但实际上其被编译后在JVM执行的对应会变代码就发现,这句话被编译成8条汇编指令,大致做了三件事情:

  1)给instance实例分配内存;

  2)初始化instance的构造器;

  3)将instance对象指向分配的内存空间(注意到这步时instance就非null了)

  如果指令按照顺序执行倒也无妨,但JVM为了优化指令,提高程序运行效率,允许指令重排序。如此,在程序真正运行时以上指令执行顺序可能是这样的:

  a)给instance实例分配内存;

  b)将instance对象指向分配的内存空间;

  c)初始化instance的构造器;

  这时候,当线程一执行b)完毕,在执行c)之前,被切换到线程二上,这时候instance判断为非空,此时线程二直接来到return instance语句,拿走instance然后使用,接着就顺理成章地报错(对象尚未初始化)。

  具体来说就是synchronized虽然保证了线程的原子性(即synchronized块中的语句要么全部执行,要么一条也不执行),但单条语句编译后形成的指令并不是一个原子操作(即可能该条语句的部分指令未得到执行,就被切换到另一个线程了)

  根据以上分析可知,解决这个问题的方法是:禁止指令重排序优化,即使用volatile变量

将变量instance使用volatile修饰即可实现单例模式的线程安全。

五、内存屏障

5.1 CPU层面如何禁止重排序

内存屏障

对某部分内存做操作时前后添加的屏障,屏障前后的操作不可以乱序执行

5.2 JSR内存屏障

 

 5.3 JVM层面volatile实现细节

 

 总之,volatile读写前后,都加屏障

5.4 hanppens-before原则

JVM规定重排序必须遵守的规则,下面的几种,必须加屏障,禁止指令重排

5.5 as if serial

不管如何重排序,单线程执行结果不会改变,不影响数据的最终一致性

六、volatile如何解决指令重排序

1: volatile i

2: ACC_VOLATILE

3: JVM的内存屏障:屏障两边的指令不可以重排,保障有序。

4:hotspot实现

bytecodeinterpreter.cpp

int field_offset = cache‐>f2_as_index();
    if (cache‐>is_volatile()) {
        if (support_IRIW_for_not_multiple_copy_atomic_cpu) {
            OrderAccess::fence(); // fence屏障
}

orderaccess_linux_x86.inline.hpp

inline void OrderAccess::fence() {
    if (os::is_MP()) {
        // always use locked addl since mfence is sometimes expensive
#ifdef AMD64
        __asm__ volatile ("lock; addl $0,0(%%rsp)" : : : "cc", "memory");
#else
        __asm__ volatile ("lock; addl $0,0(%%esp)" : : : "cc", "memory");
#endif
    }
}

lock addl

LOCK用于在多处理器中执行指令时对共享内存的独占使用。

它的作用是能够将当前处理器对应缓存的内容刷新到内存,并使其他处理器对应的缓存失效。另外还提供了有序的指令无法越过这个内存屏障的作用。

七、缓存

7.1 计算机的组成

 7.2 存储器的层次结构

 7.3 从CPU的计算单元(ALU)到:

 7.4 多核CPU

 按块读取,程序局部性原理,可以提高效率,充分发挥总线CPU针脚等一次性读取更多数据的能力。

缓存行:

缓存行越大,局部性空间效率越高,但读取时间慢缓存行越小,局部性空间效率越低,但读取时间快取一个折中值,目前多用:64字节

 

 Volatile关键视频教程

  • 10
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值