每天一点小知识——Java中Volatile和CPU的缓存

218 阅读2分钟
CPU高速缓存架构
CPU_Cache

1.每次读取数据都会从L1开始读取,L1找不到再去L2,L2找不到再去L3,L3找不到就去主内存读 2. 2.容量:L3>L2>L1 速度:L1>L2>L3

3.每次读取最小单位缓存行:64字节,可以理解为缓存中的数据是一行一行的,一次读写一行

4.保持缓存一致性:MESI协议

Volatile作用

1.防止指令重排

2.每次对变量的修改,都会将CPU核高速缓存写回到主内存

3.一个CPU核高速缓存写回到主内存会导致其它CPU核的高速缓存无效,其它CPU核读取的时候会从主内存重新读(MESI协议)

Volatile测试

1.定义一个类

public static class VolatileBean {
        volatile long a;
        volatile long b;
}

2.开启两条线程各自对a和b执行操作

VolatileBean volatileBean = new VolatileBean();
Thread t1 = new Thread(() -> {
    for (int i = 0; i < 10_000_000; i++) {
        volatileBean.a = i;
    }
});
Thread t2 = new Thread(() -> {
    for (int i = 0; i < 10_000_000; i++) {
        volatileBean.b = i;
    }
});
long start = System.currentTimeMillis();
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(System.currentTimeMillis()-start);

多次执行输出:大概在40到60之间,也有可能会输出17

3.对VolatileBean进行修改,多增加几个字段

public static class VolatileBean {
        volatile long a;
        long p1;
        long p2;
        long p3;
        long p4;
        long p5;
        long p6;
        long p7;
        volatile long b;
}

然后再执行上面步骤 2的代码 多次执行输出:大概在14到18之间

分析

为什么VolatileBean添加了几个字段之后速度提升了?

  • 上面提到过CPU缓存行是64字节,而我们的代码中long类型是8字节,一开始只有a、b两个字段的时候,一共占了16字节,在同一个缓存行中

  • 添加了p1到p7一共7个long类型后,a到p7一共8个long类型,占了64字节,填满缓存行,所以b字段只能放到另外的缓存行,保证了a和b不在同一个缓存行

    CPU_Cache
  • 上面说过了,volatile修饰的字段,修改的时候会写回到主内存并导致其它CPU核的高速缓存中对于字段的值的缓存行无效(CPU读写最小单位64字节),所以

    1. a和b在同一个缓存行,a修改会导致另外一个CPU核中a所在的缓存行无效,即b的值也无效,需要重新读取主内存
    2. a和b不在同一个缓存行,a或b的改动不会影响到另外一个值所在的缓存行无效,即下次另外一个线程读取的时候直接从高速缓存中读取就行
思考

上面说过,当只有a、b两个线程的时候,也有几率会出现花费时间为17,我认为有可能是两个线程刚好运行在同一个CPU核上,还没去验证