缓存行(Cache Line)
直接说重点,概念什么的请自行百度,用最通俗的话来讲就是多核计算机的一个处理器会有多个核,每个核中会存在L1、L2缓存,多个核之间共享L3缓存,画个简单的图来表示一下:

变量位置与访问效率对比:
| 位置 | 执行效率 |
|---|---|
| 寄存器中 | 1个周期 |
| CPU CACHE中 | 1~30个周期 |
| 主存中 | 50~200个周期 |
| 磁盘中 | 几千万个周期 |
以常见的windows操作系统来说,一个缓存行一般是64个字节的大小,cpu每次访问数据时,会逐一从L1,L2,L3中寻找,如果没有则会去主存中读取,读取不到会去磁盘中寻找,使用缓存需要发挥局部性原理,时间局部性和空间局部性。
时间局部性
例如有一个变量会被多次访问,那么把它加载到缓存中,后面多次访问都直接从缓存中命中。
空间局部性
可以充分利用64字节的大小,一次性把后续程序要用到的数据都加载进来,例如一个数组,可以把其全部加载进来,后面使用时会直接从缓存命中。
伪共享(False Sharing)
对象的成员变量在多线程环境下使用时,会出现一个伪共享False Sharing的概念。
先来复习一下基础知识
1.Java对象头都占8个字节
2.成员变量占用字节大小如下表
| 顺序 | 类型 | 字节数量 |
|---|---|---|
| 1 | double | 8字节 |
| 2 | long | 8字节 |
| 3 | int | 4字节 |
| 4 | float | 4字节 |
| 5 | short | 2字节 |
| 6 | char | 2字节 |
| 7 | boolean | 1字节 |
| 8 | byte | 1字节 |
| 9 | 对象引用 | 4或者8字节 |
| 10 | 子类字段 | 重新排序 |
那么在Java代码中如何产生伪共享呢?直接在代码中解释
public class FalseSharing {
private int x;
private int y;
private int z;
}
上述代码中x,y,z由于缓存行的局部性原理,会一次性加载到统一缓存行中

根据MESI协议,当线程A尝试修改x变量时,同时线程B尝试修改z变量,看似两个互不相干的线程操作,实际上已经产生了伪共享,线程A在core1上优先修改后,这时这个缓存行状态已经由共享状态变更为修改状态,并且发起写操作至主存,并且会通知其他cpu核此时这个缓存行已经无效,需要去主存中重新读取缓存行,之后再进行线程B的写操作,看似两个毫无相关的变量已经在多线程环境下产生了先后顺序。
伪共享的解决方案
JDK6的时代,可以通过增加一些无用的long类型变量,并且加上对象头的8个字节,刚好把内存扩展到64字节或者64的整数倍。
public final static class VolatileLong {
public volatile

本文深入探讨了多核处理器环境中缓存行的工作原理及其对性能的影响,详细讲解了伪共享现象及其解决方案,包括通过填充变量和使用@Contended注解来避免伪共享。
最低0.47元/天 解锁文章
458

被折叠的 条评论
为什么被折叠?



