上一篇:029-JVM-volatil e和指令重排

1.存储器的层次就结构


由于寄存器的速度是非常快的,是内存的100被,是硬盘的10的六次方倍。

下图是个硬件的速度指标,可以使我们对其有更为直观的认识:

从cpu到

大约需要cpu周期

大约小的时间

主存(内存)

60-80ns

QPI总线(between sockets ,ont drawn)

20ns

L3

45cycle

15ns

L2

10cycle

3ns

L1

3cycle

1ns

寄存器

1cycle

cpu读取数据,吸纳从寄存器中读取,如果无,则一次L1\L2\L3读取。

2.cache line 缓存行

那么系统读取数据是需要什么读取什么吗?当然是的,但是由于缓存行的存在,他会都去更多的数据。比如读取一个int类型4个字节的数据,他会把这四个字节后面的60个字节都读进去,即每次读64个字节的数据。这就是缓存行cache line

3.cache line 缓存行

jvm内置缓存和redis缓存 jvm缓存行_jvm


场景:

在多核cpu读取数据的时候:

  • core1 读取了x=1的数据,不好意思,由于cache line的存在,他需要把后面的y=2联通后面的数据一读进core1中;
  • core2 读取了y=2的数据,不好意思,由于cache line的存在,他需要把后面的x=1联通后面的数据一读进core2中;

此时core1 对x=1做了运算是的x=11
此时core2 对y=2做了运算是的x=22

下你在在core1和core2中的数据如下:


我们发现出现了数据不一致的问题。关于数据不一致问题,这就是伪共享的问题

4.缓存对齐可以提高效率

知道了cache line的存在,我们在写代码的时候可以利用缓存对齐(就是每次64字节)的方式去提供效率,真的可以吗?
如果每次不是64个字节,需要等待到了64个字节才会写入缓存哦!中间有等待的时间,是的效率下降。

4.1 伪共享时效率低

jvm内置缓存和redis缓存 jvm缓存行_伪共享_02

代码如下:

package com.yuhl.c2020.cacheline;

/**
 * @author yuhl
 * @Date 2021/1/2 16:04
 * @Classname One
 * @Description cacheline在同一个cup中 效率低
 */
public class One {
    private static class T {
        public volatile long x = 0L;
    }

    //含有两个Long类型的数据,每个8个字节,大概率会加载到同一个cpu中。
    public static T[] arr = new T[2];

    static {
        arr[0] = new T();
        arr[1] = new T();
    }

    public static void main(String[] args) throws Exception {
        //线程t1修改第一个long 在core1中 由于一致性的存在,需要一个机制做一致性,影响效率
        Thread t1 = new Thread(()->{
            for (long i = 0; i < 1000_0000L; i++) {
                arr[0].x = i;
            }
        });

        //线程t2修改第一个long 在core2中 由于一致性的存在,需要一个机制做一致性,影响效率
        Thread t2 = new Thread(()->{
            for (long i = 0; i < 1000_0000L; i++) {
                arr[1].x = i;
            }
        });

        final long start = System.nanoTime();
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println((System.nanoTime() - start)/100_0000);
    }
}

4.2 缓存对齐时效率高,不存在伪共享

直观看高了3倍


代码如下:

package com.yuhl.c2020.cacheline;

/**
 * @author yuhl
 * @Date 2021/1/2 16:05
 * @Classname Two
 * @Description cahcheline不在同一个cup中,效率更高
 */
public class Two {
    private static class Padding {
        public volatile long p1, p2, p3, p4, p5, p6, p7;
    }

    //继承Padding ,内衣金含有p1, p2, p3, p4, p5, p6, p7 56个字节的数据了,再加上 long x 大概路占用了一个缓存行,
    //线程2 按到T的时候前面也有p1, p2, p3, p4, p5, p6, p7 56个字节的数据了,后面才是long  x 所以 一定不存在为共享的问题,也就不存在某机制保证
    //一致性的问题了。效率会高
    private static class T extends Padding {
        public volatile long x = 0L;
    }

    public static T[] arr = new T[2];

    static {
        arr[0] = new T();
        arr[1] = new T();
    }

    public static void main(String[] args) throws Exception {
        Thread t1 = new Thread(()->{
            for (long i = 0; i < 1000_0000L; i++) {
                arr[0].x = i;
            }
        });

        Thread t2 = new Thread(()->{
            for (long i = 0; i < 1000_0000L; i++) {
                arr[1].x = i;
            }
        });

        final long start = System.nanoTime();
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println((System.nanoTime() - start)/100_0000);
    }
}

5.伪共享问题引出数据一致性问题

上面提到了伪共享时会造成数据不一致问题,那么为了保证一致性我们应该怎么办么?目前x86架构下有哪些解决方案?

6.数据一致性问题解决方案

针对一致性的问题有两种解决方案:

  1. 总线锁:在L3和L2直接加锁,拿到锁才能处理下面工作。
  2. 一致性协议MESI(缓存一致性协议)
    即通过四个状态来保证数据的一致性:

状态

解释

modify

更改过标注

exclusive

独享

shared

共享

invalid

我读取是被别的core改动过标记,最终需要我再次同步下

关于缓存一致性的详细信息可以参考:

下一篇:031-JVM-合并写(write combining)