在介绍伪共享以前,先了解一下如今经常使用的CPU架构的缓存设计,以下图:咱们常见的CPU架构里缓存都是L1 L2 L3多级缓存,其中L1 L2 在每一个CPU内核中(每一个Core都有L1 L2缓存), L3独立与全部核以外java
cpu核心读取数据老是从L1开始,L1不存在,再读取L2,L2中数据不存在再读取L3,L3数据不存在再读从内存中读取。根据网上的一些资料显示,CPU在L1缓存内读取的速度是小于1ns,L2缓存在1-2ns L3缓存在10ns左右,而从内存读取要100ns左右,而咱们的缓存存储数据都是经过缓存行来进行存储的,缓存行的大小通常都是2的倍数区间在32字节到256字节间,主流CPU的缓存行大小为64字节数组
上图是一个经典的多线程环境读取同一个缓存行里的数据的示例解析:X Y 变量被CPU缓存读取放到同一个缓存行里,Core1 核心的线程想更改X的值,Core2核心的线程想更改Y的值,由于CPU在对数据进行读取的时候遵循缓存一致性来解决高速缓存的数据不一致问题,因此每次Core1 核心线程对X的修改,就会同步一次Core2核心的L1 L2缓存里同一缓存行里的X值,每次Core2核心线程对Y的修改,也都要统一缓存行里的Y值,这样就形成了严重的性能消耗,这就是所谓的伪共享
如何解决伪共享咱们既然知道了CPU缓存行里的多个数据在多线程环境下会形成伪共享,那解决方法就是使用缓存行填充方案,不要把存在竞争关系的数据 放到同一个缓存行里。不放在同一个缓存行里,多线程竞争状况下,cpu每一个Core核心的L1 L2缓存就不会强制缓存同步形成性能损耗
JAVA解决方案缓存
例如,多线程环境下对TestLong里的vaue字段进行更新public class TestLong{ public volatile long p1, p2, p3, p4, p5, p6; // 缓存行填充,自定义6个long类型变量 public volatile long value = 0L; .......}缓存行填充方案:64位系统Java 程序的对象头固定占 16个 字节(32位系统是8个字节),因此咱们只须要填 6 个无用的Long类型补上6*8=48字节凑满64位,不过这里有个兼容问题,这套代码也就能针对64位系统中运行。多线程
jdk8中提供了@Contended注解来解决伪共享问题。该注解会自动根据运行代码平台的cpu的缓存行大小进行设置缓存行填充方案固然:要使用@Contended 注解 必须增长JVM参数 -XX:-RestrictContended测试代码(base:Java8)架构
package com.fqh.review.base.cacheLine;import sun.misc.Contended;/** * @author fqh * @Description: Java8缓存行填充测试类--比较测试须要放开LongArr变量的Contended注解增长- XX:-RestrictContended JVM参数 * @date 2020/7/27下午5:44 */public class CacheLinePadding {
//@Contendedpublic static volatile long[] longArr=new long[2];
public static void main(String[] argus) throws InterruptedException { Thread t1=new Thread(() -> {for(long i=0;i<5000000000L;i++){//50亿次 longArr[0]=i; } }); Thread t2=new Thread(() -> {for(long i=0;i<5000000000L;i++){ longArr[1]=i; } });long startTime = System.nanoTime(); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println((System.nanoTime()-startTime)/100_0000);//运行时间秒 }}
处理器名称: Intel Core i7处理器速度: 2.7 GHz处理器数目: 1核总数: 4L2 缓存(每一个核): 256 KBL3 缓存: 8 MB内存: 16 GB
工做中的实际使用app