在java多线程内存模型中,每个线程占用一个处理器内核,共同与唯一的主存交互数据,但线程操作数据不会直接操作主存中的数据,而是直接与每个内核的缓存交互,所以线程对数据的操作仅仅作用在缓存中的备份,如果没有同步给主存,则会一直使用该备份数据(失效过期了可能也不知道),在多线程的情况下可能会导致一个线程对数据的操作另一个线程不可见(不知道数据被修改了),于是需要使用volatile关键字来标记可能会被多线程操作的对象,保证多线程下变量的可见性。

volatile作用原理:

  1. 将缓存数据立即写回主存
  2. 这个写回主存的操作会引起其他cpu内核里缓存了该主存地址的数据失效
  3. 提供内存屏障(使用汇编的lock前缀实现)使lock前后指令不会重排序

指令重排:
as-if-serial:重排序不影响单线程程序执行结果
happens-before原则:锁规则、volatile规则、线程启动规则、传递性、线程终止规则、线程中断规则、对象终结规则

传统的DCL实现的单例模式可能会因为指令重排序导致对象不为空但为零值

if(object==null){
    synchronized(ClassName.class){
        if(object==null){
            new;
        }
    }
}

上述代码在java编译执行时可能会发生指令重排序,对象在创建时要先创建,再分配内存,但是重排序可能颠倒这两步,则会导致另一个线程访问对象时发现对象存在,但是读取到的对象并不是创建完成的对象,只是一个初始化为零值的对象。

对象创建流程:类加载检查,未加载则加载类,加载了则分配内存,初始化零值,设置对象头,执行init方法。

解决方法:在该属性声明的时候添加volatile关键字,实现内存屏障

内存屏障类型:loadload、storestore、loadstore、storeload

jvm中内存屏障使用lock前缀实现,intel的cpu有硬件级的内存屏障指令:ifence、sfence、mfence

标签: none

添加新评论