volatile解析

工作模型

缓存一致性协议 (MESI)

modify :修改状态,表示共享数据只缓存在当前 CPU 并且是修改状态,也就是缓存的数据与主内存中不一致

shared:共享状态,多个 CPU 缓存中的内容与主内存中内容一致

invalid :失效状态,某个CPU 缓存中的内容进行了修改,对将其余 CPU 缓存中的数据设为 Invalid 状态

exclusively:独有状态,只有某个 CPU 缓存有这个数据,并且数据没有进行修改

缓存一致性协议,通过设置缓存的几种状态,用来告知应该从哪里读写数据,在演进的过程中存在堵塞的情况。

当我们对某个 CPU 的缓存数据进行更新时,当前 CPU 需要与其他 CPU 进行一个通信,告知其他 CPU 将该缓存值设置为 I状态(Invalid),在收到其他 CPU 全部的确认之后,才会继续往下执行指令,在这段时间中一直是处于一个阻塞的状态,影响性能。

随后,出现了 storebuffer ,CPU 将 M 状态的数据写入到该文件中,并向其余 CPU 发送失效通知,这时,无需等待其余 CPU 将信息返回,可以接着往下执行指令,但相应的,带来了代码乱序执行的问题

	int value = 3;

    public void cpu0() {
        value = 10;                 (1)
        boolean flag = true;        (2)

    }

    public void cpu1() {
        if (flag) {                 (3)
            assert value == 10;     (4)
        }
    }

flag 值只存在于 CPU0 中,是一个 E 状态,value 处于一个 S 状态

(1)对 value 进行更改,状态由 S -》M,同时将操作写入到 storebuff 文件中,并发送失效通知

(2)flag 赋值

(3)读取 flage 值,由于 flag 属于一个独占状态,所以只能从主内存中去获取,也就是 if (flag) = true

(4)由于 (1) 进行的操作需要同步和通知,是一个异步操作,因此在执行到 (4) 的时候,同步可能并没有完成,读取的依旧是当前 value 的缓存值 3,也就是 assert value == 10 返回结果为 false

上述结果,与我们实际想要的结果并不符合,因为 storebuffer 的异步操作,导致代码的乱序执行,影响了最后的结果,这时 CPU 提供了一个内存屏障,比如在 (1,2)之间,插入一个 Store Memory Barrier 写屏障,要求将数据强制刷入主内存中,在(3,4)之前插入一个 Load Memory Barrier 读屏障,要求强制读取主内存中的数据,这样也就解决了乱序执行的问题,但这就相当于绕过了 CPU 为了优化而引入了 高速缓存。

JMM

  • JMM ,Java 内存模型,基本工作原理和 CPU 一致

主内存中存放一些堆中可以共享的数据内容,每个线程都有自己的工作内存为线程私有,所有的计算等操作需要在工作内存中完成

Java 内存模型简单来说就是通过插入内存屏障来达到禁止重排序的功能,编译器根据具体的底层架构执行相对应的 CPU 指令,对于编译器来说,内存屏障会禁止指令重排序,CPU的内存屏障会强制读写缓存

  • JMM 提供了一些解决可见性和重排序的方法

    • volatile()
    • synchronized
    • final
  • 重排序问题

    ​ 编译器对于更好的利用 CPU 会对代码进行重排序,重排序的规则是不影响单线程下运行的结果

    ​ CPU 也会对指令进行相关的重排序操作,以便更有效率的执行

  • 为了保证内存可见性,Java编译器在生成指令的时候,会插入一些适当的屏障来完成

volatile

增加一个 volatile 的参数,通过 javap -v class.class,然后我们找到相对应的源码

// accessFlag.hpp
class AccessFlags VALUE_OBJ_CLASS_SPEC {
  friend class VMStructs;
 private:
  jint _flags;

 public:
  // Java access flags
  bool is_public      () const         { return (_flags & JVM_ACC_PUBLIC      ) != 0; }
  bool is_private     () const         { return (_flags & JVM_ACC_PRIVATE     ) != 0; }
  bool is_protected   () const         { return (_flags & JVM_ACC_PROTECTED   ) != 0; }
  bool is_static      () const         { return (_flags & JVM_ACC_STATIC      ) != 0; }
  bool is_final       () const         { return (_flags & JVM_ACC_FINAL       ) != 0; }
  bool is_synchronized() const         { return (_flags & JVM_ACC_SYNCHRONIZED) != 0; }
  bool is_super       () const         { return (_flags & JVM_ACC_SUPER       ) != 0; }
  // is_volatile
  bool is_volatile    () const         { return (_flags & JVM_ACC_VOLATILE    ) != 0; }
  bool is_transient   () const         { return (_flags & JVM_ACC_TRANSIENT   ) != 0; }
  bool is_native      () const         { return (_flags & JVM_ACC_NATIVE      ) != 0; }
  bool is_interface   () const         { return (_flags & JVM_ACC_INTERFACE   ) != 0; }
  bool is_abstract    () const         { return (_flags & JVM_ACC_ABSTRACT    ) != 0; }
  bool is_strict      () const         { return (_flags & JVM_ACC_STRICT      ) != 0; }

接着我们找到这个方法对应的调用逻辑,可以发现,它通过调用 OrderAccess 增加了内存屏障

if (cache->is_volatile()) {
            if (tos_type == itos) {
              obj->release_int_field_put(field_offset, STACK_INT(-1));
            } else if (tos_type == atos) {
              VERIFY_OOP(STACK_OBJECT(-1));
              obj->release_obj_field_put(field_offset, STACK_OBJECT(-1));
              OrderAccess::release_store(&BYTE_MAP_BASE[(uintptr_t)obj >> CardTableModRefBS::card_shift], 0);
            } else if (tos_type == btos) {
              obj->release_byte_field_put(field_offset, STACK_INT(-1));
            } else if (tos_type == ltos) {
              obj->release_long_field_put(field_offset, STACK_LONG(-1));
            } else if (tos_type == ctos) {
              obj->release_char_field_put(field_offset, STACK_INT(-1));
            } else if (tos_type == stos) {
              obj->release_short_field_put(field_offset, STACK_INT(-1));
            } else if (tos_type == ftos) {
              obj->release_float_field_put(field_offset, STACK_FLOAT(-1));
            } else {
              obj->release_double_field_put(field_offset, STACK_DOUBLE(-1));
            }
    		// 新增内存屏障
            OrderAccess::storeload();

由次可以,volatile 提供了四种内存屏障

// orderAccess
inline void OrderAccess::loadload()   { acquire(); }
inline void OrderAccess::storestore() { release(); }
inline void OrderAccess::loadstore()  { acquire(); }
inline void OrderAccess::storeload()  { fence(); }
  • loadload:step1 loadload step2 :表示步骤1的数据加载优先于步骤2及其之后的操作
  • storestore:step1 storestore step2:确保步骤1的写入对其他处理器优先于步骤2的写入
  • loadstore:step1 loadstore step2:确保步骤1的数据状态优先于步骤2的指令执行
  • storeload:step1 storeload step2 :全屏障
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
  }
}

我们观察 storeload() 屏障调用的 fence 方法,可以看到,从JVM层面调用 volatie 进行重排序,在底层通过 lock 指令保证CPU层面的可见性和重排序问题

happens-before

  • 顺序执行原则

    • 一个线程中的操作 happends-before 该线程下后面的操作,也就是单线程下代码顺序不管怎么变,结果不变
  • volatile 规则

    • 对于 volatile 修饰的变量的写操作 happends-before 之后的读操作
  • 传递规则

    • 1 happends-before 2,2 happends-before 3,所有 1 happends-before 3
  • start 规则

    • start 启动之前的操作 对于 线程可见
  • join 规则

    • join 启动之前的操作 对于 线程阻塞
  • 监视器规则

    • 锁对象的释放 happends-before 锁对象的加锁

happends-before :表示 前者的操作对后者可见

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值