首先,计算机工程师为了提高CPU的利用率,平衡CPU与内存之间的速度差异,在CPU里面设计了三级缓存。CPU在向内存发起IO操作的时候,一次性会读取64个字节的数据作为一个缓存行,缓存到CPU的高速缓存里面。
在Java中一个long类型是8个字节,意味着一个缓存行可以存储8个long类型的变量。这个设计是基于空间局部性原理来实现的,也就是说,如果一个存储器的位置被引用,那么将来它附近的位置也会被引用。
所以缓存行的设计对于CPU来说,可以有效地减少和内存的交互次数,从而避免了CPU的IO等待,以提升CPU的利用率。正是因为这种缓存行的设计,导致如果多个线程修改同一个缓存行里面的多个独立变量的时候,基于缓存一致性协议,就会无意中影响了彼此的性能,这就是伪共享。
比如,CPU0上运行的线程想要更新变量X,CPU1上的线程想要更新变量Y,而X/Y/Z都在同一个缓存行里面。每个线程都需要去竞争缓存行的所有权对变量做更新,基于缓存一致性协议。一旦运行的某个CPU上的线程获得了所有权并执行了修改,就会导致其他CPU中的缓存行失效。这就是伪共享问题的原理。
解决办法有两个:
使用对齐填充。因为一个缓存行大小是64个字节,如果读取的目标数据小于64个字节,可以增加一些无意义的成员变量来填充。
在Java8里面提供了@Contented注解。它也是通过缓存行填充来解决伪共享的问题。被@Contented注解声明的类或者字段,会被加载到独立的缓存行上。
领取专属 10元无门槛券
私享最新 技术干货