目录
- 1.背景简介
- 2.伪共享问题
- 3.问题解决
- 4.JDK使用示例
1.背景简介
-
我们知道,CPU 是不能直接访问内存的,数据都是从高速缓存中加载到寄存器的,高速缓存又有 L1,L2,L3 等层级。在这里,我们先简化这些复杂的层级关系,假设只有一级缓存和一个主内存。
-
CPU 读取和更新缓存的时候,是以行为单位进行的,也叫一个
cache line
,一行一般 64 字节,也就是8个long的长度。
2.伪共享问题
一个缓存行可以放多个变量,如果多个线程同时访问不同的变量,而这些不同的变量又恰好位于同一个缓存行,那么会发生什么呢?
如上图所示,X 和 Y 为相邻 2 个变量,位于同一个缓存行,两个 CPU core1 和 core2 都加载了它们。
core1 更新 X,同时 core2 更新 Y,由于数据的读取和更新是以 缓存行 为单位的,这就意味着当这2件事同时发生时,就产生了竞争,导致 core1 和 core2 有可能需要重新刷新自己的数据(缓存行被对方更新了),最终导致系统的性能大打折扣,这就是伪共享
问题。
3.问题解决
如下图:
如上图所示,我们把 X 单独占用一个缓存行,Y 单独占用一个缓存行,这样各自更新和读取,就不会有任何影响了。
对应的操作就是在代码中使用 @sun.misc.Contended
注解。
4.JDK使用示例
例如在 JDK1.7 中 Thread
类中为 ThreadLocalRandom
增加的三个成员变量:
/**
* 线程本地随机的当前种子。
*/
@sun.misc.Contended("tlr")
long threadLocalRandomSeed;
/**
* 探测哈希值;如果线程本地随机种子被初始化,那么该值也非0。使用缓存填充(注解方式)来避免伪共享。
*/
@sun.misc.Contended("tlr")
int threadLocalRandomProbe;
/**
* 从公共线程本地随机序列中分离的二级种子。
*/
@sun.misc.Contended("tlr")
int threadLocalRandomSecondarySeed;
@sun.misc.Contended("tlr")
就会在虚拟机层面,帮助我们在变量的前后生成一些 padding
,使得被标注的变量位于同一个缓存行,不与其他变量冲突。
在 Thread 对象中,threadLocalRandomSeed
、threadLocalRandomSeed
、threadLocalRandomSecondarySeed
被标记为同一组 tlr
,使得这 3 个变量放置于一个单独的缓存行,而不与其它变量发生冲突,从而提高在并发环境中的访问速度。
整理完毕,完结撒花~