StoreStore 屏障: 对于这样的语句 Store1; StoreStore; Store2, 在 Store2 及后续写入操作执行前, 保证 Store1 的写入操作对其它处理器可见.
StoreLoad 屏障: 对于这样的语句 Store1; StoreLoad; Load2, 在 Load2 及后续所有读取操作执行前, 保证 Store1 的写入对所有处理器可见. 它的开销是四种屏障中最大的. 在大多数处理器的实现中, 这个屏障是个万能屏障, 兼具其它三种内存屏障的功能
LoadLoad 屏障: 对于这样的语句 Load1; LoadLoad; Load2, 在 Load2 及后续读取操作要读取的数据被访问前, 保证 Load1 要读取的数据被读取完毕. 也就是
LoadStore 屏障: 对于这样的语句 Load1; LoadStore; Store2, 在 Store2 及后续写入操作被刷出前, 保证 Load1 要读取的数据被读取完毕.
上面四个屏障不但会强制变量刷新, 而且会防止指令之间的重排序 (JVM 对于没有数据依赖关系的指令会进行重排序, 指令重排序也会导致变量的值读取是错误的).volatile 的底层就是这样实现, 较 synchronized 更为轻量级, 毕竟 synchronized 是用来修饰方法和代码块的, 而 volatile 保证的只是一个变量, 所以更为轻量级.
如何优化 volatile 关键字?
我们先来弄清楚对于英特尔酷睿 i7, 酷睿, Atom 和 NetBurst, 以及 Core Solo 和 Pentium M 处理器的 L1,L2 或 L3 缓存的高速缓存行是 64 个字节宽, 不支持部分填充缓存行. 要想优化 volatile 变量运行速度, 只需要将变量追加到 64 个字节
也就是如果一个 volatile 变量存入高速缓存且不足 64 个字节长度, 这时候可能在同一个缓存行中就会再存入另一个 volatile 变量, 而由于缓存一致性机制, 当处理第一个 volatile 变量的时候, 整个缓存行是锁定的, 这时候第二个 volatile 变量或者缓存行其他变量需要被其他处理器操作就必须等到第一个 volatile 变量操作完才能进行. 如果这时 volatile 变量是 64 个字节独占一个缓存行的时候, 那么就不会有上面的影响发生.
那么什么时候我们都需要追加 64 个字节吗? 有下面两种情况不需要追加
1, 缓存行非 64 字节宽的处理器. 如 P6 系列和奔腾处理器, 它们的 L1 和 L2 高速缓存行是 32 个字节宽.
2, 共享变量不被频繁地写. 可以看出追加字节的方式是需要性能消耗的, 如果共享变量不被频繁地写的话, 锁定的概率小, 不需要追加字节.
总结
volatile 是修饰变量的一个关键字, 写在基本数据类型之前. 用来保证这个变量在多线程的环境下具有可见性. 是通过内存屏障和禁止指令重排序来实现的, 但是不具有原子性. CAS 和 synchronized 才会保证原子性.
来源: https://www.cnblogs.com/Cubemen/p/10767255.html