JVM伪共享问题

伪共享

在现在的 CPU 架构下,一般地,一台计算机都有多个 CPU 核心,叫作多核 CPU,这些 CPU 都要从一块叫作内存的地方读取数据,经过加工处理,再写回到内存中,如果每次读写数据都跟内存进行交互,太慢了,你可以想像成内存跟硬盘的关系,所以,为了加快 CPU 的处理速度,人们就给 CPU 安上了缓存,一般地,现代处理器都具有三级缓存,这三缓存也有个关系,越接近 CPU 的缓存越快越贵容量越小,越远离 CPU 的缓存越慢越便宜容量越大。

比如,对于一台 4 核 CPU 的计算机,它的缓存布局可能是这样的:
在这里插入图片描述
这样,在处理数据的时候,CPU 就加载内存中的一小块数据到 CPU 缓存中,处理完毕并不用立马写回内存,等下次再读取或修改同一片内存区域的数据时,直接走缓存就好了,这样就极大地提高了数据处理的速度。刚才有提到每次加载一小块数据,那么,这个 “一小块” 是多大呢?通常地,现代 CPU 架构为 64 个字节。在这里插入图片描述
似乎很完美,试想这样一个问题,在内存中有两个相临的变量 x 和 y,一个线程一直在对 x 进行 ++ 操作,一个线程一直在对 y 进行 ++ 操作,会出现怎样地后果呢? 在这里插入图片描述
假设两个变量初始值为 0,各自增 100 次,因为是两个不同的线程处理,所以这两个线程可能处于不同的 CPU 核中,根据上面的理论,CPU 每次加载 64 字节的数据到缓存中,所以,x 和 y 始终一起被加载到不同的缓存中,那么,各自修改完了如何写回主内存呢?发现没法写回了是不是?因为写回也是整个缓存行一起写回的,不管先写回哪个,都会被后写回的覆盖。

为了解决这种问题,有两种策略:
1 给这个缓存行对应的内存块加锁,每次读写数据的时候都从主内存重新读取,写完之后立马写回主内存,多个线程处理同一块内存区域数据的时候排队进行,这样数据肯定就准确了;

2 把 x 和 y 分隔开,不要让它们相临,让它们始终不会同时被加载到同一个缓存行中,只需要在它们之间补足 64 字节,它们自然就被隔开了,永远不会加载到同一个缓存行中;

两种方案都是可行的。

对于第一种方案,相当于缓存行永远失效,形同虚设了,这种锁又有另外一个名字 —— 内存屏障(Memory Barrier)或者内存栅栏 (Memory Fence),在现代 CPU 架构下,内存屏障主要分为读屏障(Load Memory Barrier)和写屏障(Store Memory Barrier):
1 读屏障,每次都从主内存读取最新的数据;
2 写屏障,将缓存写入到主内存;

内存屏障还有个重要的功能,防止重排序,即不会把内存屏障前后的指令进行重排序。

使用内存屏障这种技术,又引来了新的问题,每次对 x 的操作,同时对 y 产生了影响,反之亦然,相当于 x 和 y 变成了一种共生的状态,但是实际上他们却没有任何关系,这种不同线程对同一块内存区域(缓存行)的不同变量的操作产生了互相影响的现象,就叫作伪共享(False Sharing)。为了解决伪共享带来的问题,就引出了第二种方案。

对于第二种方案,这样的玩法叫作加 Padding,在两个变量之间加一系列无用的变量,使得两个变量永远不会被加载到同一个缓存行,但是,它也有个问题,试想如果两个线程同时修改 x,它就无法处理了,此时,就只能使用第一种方案了。

在 Java 中,这种加 Padding 的玩法主要有三种实现方式:

1 变量前后添加 N 个 long 类型,N 的取值有两种说法,一种是 7,一种是 15,因为内存布局是按 8 字节对齐的,所以加上 7 个 long 正好等于 64 字节,也就是一个缓存行的大小,可以保证这个变量与其它变量分隔开,15 的说法是为了避免相邻扇区预取导致的伪共享冲突,在 Disruptor 框架中使用的是 7,在 jctools 中使用的是 15;

2 使用继承且在父子类中加上 padding,这样是为了防止内存布局重排序,比如,下面这个类,会把 byte 类型的 b 存储在 long 类型的前面,因为对象头占用 12 字节(压缩后),byte 类型占用 1 字节,这样只需要被 3 个字节就可以了,如果不做这种重排序,对象头需要补齐 4 个字节,而 byte 类型需要补齐 7 个字节,造成空间浪费;

3 使用 @sun.misc.Contended 注解,不过这是 Java8 新增的注解,所以,无法兼容之前的版本,现在大部分开源框架还没有使用这个注解;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值