CPU在向内存发起IO操作的时候,一次性会读取64个字节的数据作为一个缓存行,缓存到CPU的高速缓存里面, 在Java中一个long类型是8个字节,意味着一个缓存行可以存储8个long类型的变量
由于存放到CPU缓存行的是内存块而不是单个变量,所以可能会把多个变量存放到 同一个缓存行 中, 当多个线程同时修改这个缓存行里面的多个变量时,由于同时只能有一个线程操作缓存行, 此时有两个线程同时修改同一个缓存行下的两个不同的变量,这就是伪共享
当出现伪共享时,CPU必须清空该级缓存行,会造成CPU比较大的开销。一旦运行在某个CPU上的线程获得了所有权并执行了修改,就会导致其他CPU中的缓存行失效。
这个问题的解决办法有两个:
-
使用对齐填充,因为一个缓存行大小是64个字节,如果读取的目标数据小于64个字节,可以增加一些无意义的成员变量来填充。
-
在Java8里面,提供了@Contented注解,它也是通过缓存行填充来解决伪共享问题的,被@Contented注解声明的类或者字段,会被加载到独立的缓存行上。
在默认情况下,@Contended 注解只用于 Java 核心类, 比如rt包下的类
如果用户类路径下的类需要使用这个注解, 需要添加JVM 参数:- XX:-RestrictContended。 填充的宽度默认为 128 ,要自定义填充宽度则可以通过参数 -XX:ContendedPaddingWidth 参数进行设置。
在Netty里面,有大量用到对齐填充的方式来避免伪共享问题