内存可见性 和 重排序
内存可见性
Store Buffer的延迟
- 线程a 首先获取共享变量 x 的值,L1 L2缓存没有 去L3 获取也获取不到,所以去主内存 然后获取到了值 比如是0 即 x=0;然后线程a修改x 的值为1,此时 线程a的L1 L2 L3 和主内存都为1
- 线程b 获取x的值,线程b的L1 L2缓存没有值,去L3获取到了值为1 ,此时将其修改为2 此时 线程b的L1 L2和L3 和 主内存都为2
- 线程a 又需要获取变量x 然后在自己的L1 L2缓存中获取为1,问题发生了 ,明明线程b已经将变量修改为2为什么获取的x为1呢? 这就是共享变量的内存不可见,也就是 线程b写入的值对线程a 不可见
重排序
Store Buffer的延迟写入是重排序的一种,称为内存重排序(Memory Ordering)。除此之外,还 有编译器和CPU的指令重排序
重排序类型:
- 编译器重排序。
对于没有先后依赖关系的语句,编译器可以重新调整语句的执行顺序。 - CPU指令重排序。
在指令级别,让没有依赖关系的多条指令并行。 - CPU内存重排序。
CPU有自己的缓存,指令的执行顺序和写入主内存的顺序不完全一致。
- 编译器重排序。
如何解决重排序
- final关键字
- final关键字也有相应的happen-before语义
- volatile
- synchronized
volatile
volatile的三重功效:64位写入的原子性、内存可见性和禁止重排序
- 线程在写入变量时 不会把值缓存在寄存器或者其他地方,而是直接把值刷新到主内存中
- 线程在读取该共享变量的时候,不会使用当前线程的工作内存中的值 而是直接去主内存中获取最新值
happen-before
使用happen-before描述两个操作之间的内存可见性。
- 如果A happen-before B,意味着A的执行结果必须对B可见,也就是保证跨线程的内存可见性
- happen-before只确保如果A在B之前执行,则A的执行结果必须对B可见
- 定义了内存可见性的约束,也就定义了一系列重排序的约束
- 除了这些基本的happen-before规则,happen-before还具有传递性,即若A happen-before B,B happen-before C,则A happen-before C。
happen-before规则总结
- 单线程中的每个操作,happen-before于该线程中任意后续操作。
- 编译器和CPU只能保证每个线程的as-if-serial语义
- 对volatile变量的写,happen-before于后续对这个变量的读。
- 对synchronized的解锁,happen-before于后续对这个锁的加锁。
- 对final变量的写,happen-before于final域对象的读,happen-before于后续对final变量的读。
四个基本规则再加上happen-before的传递性,就构成JMM对开发者的整个承诺。在这个承诺以外
的部分,程序都可能被重排序,都需要开发者小心地处理内存可见性问题。
