前言

首先知道CPU和cahce line,然后基于此了解下CPU如何读取数据信息。

Cache

数据流向:CPU—> L1 cache —> L2 cache —> L3 cache —> 主存
数据查找:CPU先从L1 cache找,找到返回;找不到就L2 cache找,找到返回L1 cache和CPU,以此类推。

Cache Line

Cache由很多的cache line组成,通常64 btye。
CPU每次拉取数据时,会把相邻数据也拉取,存入同一个cache line。

伪共享

当我们有多个变量时,CPU读取时,会把他们放到同一个cache line,虽然这几个变量之前没有太多关系。不同CPU修改这几个变量时,都会导致之前读取的cache line失效。这样没有充分使用缓存行特性的现象,就是伪共享。

解释:
一个cache line可以被多个不同的线程使用,当其他线程修改了cache line中的一个数值时,其他线程将会强制重新加载cache line,虽然并没有修改其他数值,这是CPU对齐cache line导致的。

单CPU情景:
每个cache line读取一些数据,分多个cache line读取。
image.png
如果数据超了,会清除原来的数据,进行覆盖填写。
image.png
如果数据要修改,只需要对cache line中的数据进行修改即可。
image.png
多核CPU:
当有多核CPU时,修改数据了,会造成其他CPU中缓存的数据也失效了。
image.png
如果多核CPU进行数据修改,那么其他的核的数据会被设置为invalid。
image.png
伪共享情况:
同时读取a和b时,cache line都被修改了,互相发生invalid,然后不断读取数据。
image.png

解决办法:Padding

为了减少false sharing,可以采取填充数据结构的形式,构造多个无用的数据,来填充凑够一个cache line。

解决办法:@Contended

Java8中引入了contended注解,可以让他们在不同的cache line。使用这个注解必须在 JVM 启动的时候加上 -XX:-RestrictContended 参数,其实也是用空间换取时间。

  1. jdk6 --- 32 位系统下
  2. public final static class VolatileLong
  3. {
  4. public volatile long value = 0L;
  5. public long p1 p2 p3 p4 p5 p6; // 填充字段
  6. }
  7. jdk7 通过继承
  8. public class VolatileLongPadding {
  9. public volatile long p1 p2 p3 p4 p5 p6; // 填充字段
  10. }
  11. public class VolatileLong extends VolatileLongPadding {
  12. public volatile long value = 0L;
  13. }
  14. jdk8 通过注解
  15. @Contended
  16. public class VolatileLong {
  17. public volatile long value = 0L;
  18. }

参考