image.png

内存可见性 和 重排序

内存可见性

Store Buffer的延迟

  1. 线程a 首先获取共享变量 x 的值,L1 L2缓存没有 去L3 获取也获取不到,所以去主内存 然后获取到了值 比如是0 即 x=0;然后线程a修改x 的值为1,此时 线程a的L1 L2 L3 和主内存都为1
  2. 线程b 获取x的值,线程b的L1 L2缓存没有值,去L3获取到了值为1 ,此时将其修改为2 此时 线程b的L1 L2和L3 和 主内存都为2
  3. 线程a 又需要获取变量x 然后在自己的L1 L2缓存中获取为1,问题发生了 ,明明线程b已经将变量修改为2为什么获取的x为1呢? 这就是共享变量的内存不可见,也就是 线程b写入的值对线程a 不可见

    重排序

    Store Buffer的延迟写入是重排序的一种,称为内存重排序(Memory Ordering)。除此之外,还 有编译器和CPU的指令重排序

    重排序类型:

    1. 编译器重排序。
      对于没有先后依赖关系的语句,编译器可以重新调整语句的执行顺序。
    2. CPU指令重排序。
      在指令级别,让没有依赖关系的多条指令并行。
    3. CPU内存重排序。
      CPU有自己的缓存,指令的执行顺序和写入主内存的顺序不完全一致。

如何解决重排序

  • final关键字
    • final关键字也有相应的happen-before语义
  • volatile
  • synchronized

    volatile

    volatile的三重功效:64位写入的原子性、内存可见性和禁止重排序

  1. 线程在写入变量时 不会把值缓存在寄存器或者其他地方,而是直接把值刷新到主内存中
  2. 线程在读取该共享变量的时候,不会使用当前线程的工作内存中的值 而是直接去主内存中获取最新值

    happen-before

    使用happen-before描述两个操作之间的内存可见性。

    1. 如果A happen-before B,意味着A的执行结果必须对B可见,也就是保证跨线程的内存可见性
    2. happen-before只确保如果A在B之前执行,则A的执行结果必须对B可见
    3. 定义了内存可见性的约束,也就定义了一系列重排序的约束
    4. 除了这些基本的happen-before规则,happen-before还具有传递性,即若A happen-before B,B happen-before C,则A happen-before C。

happen-before规则总结

  1. 单线程中的每个操作,happen-before于该线程中任意后续操作。
    1. 编译器和CPU只能保证每个线程的as-if-serial语义
  2. 对volatile变量的写,happen-before于后续对这个变量的读。
  3. 对synchronized的解锁,happen-before于后续对这个锁的加锁。
  4. 对final变量的写,happen-before于final域对象的读,happen-before于后续对final变量的读。

四个基本规则再加上happen-before的传递性,就构成JMM对开发者的整个承诺。在这个承诺以外
的部分,程序都可能被重排序,都需要开发者小心地处理内存可见性问题。
image.png