java内存模型

CPU工作原理:

  1. 单核CPU工作原理-高速缓存:

    image-20210512080222407

    1. cpu读取数据时按照L1,L2,L3,物理内存的顺序依次查找
    2. L1,L2,L3的速度依次递减,容量依次递增
  2. 多核CPU工作原理-高速缓存:

    image-20210512080411417

并发编程问题:

  1. 缓存一致性问题:多核多线程并行执行导致L1,L2缓存数据不一致

    image-20210512080648835

    1. 举个栗子:

      image-20210512081731384

      结果分析:

      image-20210512081848227

编译器优化和指令重排:

image-20210512081953496

上述1属于编译器重排序,2,3属于处理器重排序。重排序会导致多线程程序出现内存可见性问题。

举个栗子-重排序:执行完成后 i = 1或者0

public class CpuReOrder {

    private int a = 0;
    private boolean flag = false;
    public void write(){
        a = 1;          //1
        flag = true;    //2

    }
    public void read(){
        if(flag){       //3
            int i = a; //4
            System.out.println(i);
        }
    }
    public static void main(String[] args){

        final CpuReOrder cpuReOrder = new CpuReOrder();
        //写线程
        new Thread(()->cpuReOrder.write()).start();
        //读线程
        new Thread(()->cpuReOrder.read()).start();
    }
}

java内存模型

  1. 重排序-数据依赖性:

    image-20210512083136999

    image-20210512084419779

    img

  2. As-if-serial:

    image-20210512084750078

    image-20210512084827950

并发编程的问题:

  1. 原子性是指在一个操作中就是cpu不可以在中途暂停然后再调度,既不被中断操作,要不执行完成,要不就不执行。

    1. x=10; y=x; x++; x=x+1;x=new x(); 哪些操作具有原子性?
  2. 可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。(volatile,锁)

  3. 有序性即程序执行的顺序按照代码的先后顺序执行。(happens-before)

    原子性、可见性、有序性是一个抽象的概念。其底层问题就是前面提到的硬件层面的缓存一致性问题、处理器优化问题和指令重排问题。

    缓存一致性问题其实就是可见性问题。而处理器优化是可以导致原子性问题的(指令优化执行)。指令重排即会导致有序性问题。

    举个栗子:执行结束后:count=?

    image-20210512085306133

内存模型

  1. 计算机内存模型:为了保证共享内存的正确性(可见性、有序性、原子性),内存模型定义了共享内存系统中多线程程序读写操作行为的规范。通过这些规则来规范对内存的读写操作,从而保证指令执行的正确性。它与处理器有关、与缓存有关、与并发有关、与编译器也有关。它解决了CPU多级缓存、处理器优化、指令重排等导致的内存访问问题,保证了并发场景下的一致性、原子性和有序性。

  2. 内存屏障:每个CPU都会有自己的缓存(有的甚至L1,L2,L3),缓存的目的就是为了提高性能,避免每次都要向内存取。但是这样的弊端也很明显:不能实时的和内存发生信息交换,分在不同CPU执行的不同线程对同一个变量的缓存值不同。用volatile关键字修饰变量可以解决上述问题,那么volatile是如何做到这一点的呢?那就是内存屏障,内存屏障是硬件层的概念,不同的硬件平台实现内存屏障的手段并不是一样,java通过屏蔽这些差异,统一由jvm来生成内存屏障的指令。

    1. 硬件层的内存屏障分为两种:Load Barrier 和 Store Barrier即读屏障和写屏障。内存屏障有两个作用:阻止屏障两侧的指令重排序;强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效。
    2. 对于Load Barrier来说,在指令前插入Load Barrier,可以让高速缓存中的数据失效,强制重新从主内存加载数据;对于Store Barrier来说,在指令后插入Store Barrier,能让写入缓存中的最新数据更新写入主内存, 让其他线程可见。
  3. **java内存屏障:**java的内存屏障通常所谓的四种即LoadLoad,StoreStore,LoadStore,StoreLoad实际上也是Load,store两种的组合,完成一系列的屏障和数据同步功能。

    1. LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
    2. StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
    3. LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
    4. StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。 它的开销是四种屏障中最大的。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能。
  4. 数据竞争:

    image-20210512161055987

  5. 顺序一致性内存模型:

    image-20210512161252976

  6. 顺序一致性模型中程序执行示意图

    image-20210512161357355

    正确同步的程序:

    image-20210513073807001

    JMM与顺序一致性模型执行效果对比:

    image-20210513073843244

  7. **happens before规则:**在JMM中要保证一个操作的结果对另一个操作可见,那么这两个操作(可能是多个线程)之间要存在happens-before关系。

    1. 如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而
      且第一个操作的执行顺序排在第二个操作之前。
    2. 两个操作之间存在happens-before关系,并不意味着Java平台的具体实现必须要按照happens- before关系指定的顺序来执行。如果重排序之后的执行结果,与按happens-before关系来执行的结果一致,那么这种重排序并不非法(也就是说,JMM允许这种重排序)
  8. happens-before 具体规则:

    1. 程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续的操作。
    2. 监视器锁的规则:锁的解锁happens-before随后对它的加锁。
    3. volatile变量的规则:对一个volatile域的写happens-before于任意后续对它的读。
    4. 传递性:A happens-before B, B happens-before C,那么A happens-before C。
    5. start()规则:线程A执行操作ThreadB.start(),那么A线程的ThreadB.start()操作happens-before 于B线程的任意操作。这意味着:线程A在执行ThreadB.start()之前对共享变量所做的修改,在线程B执行后都将对B可见。
    6. join()规则:线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before 于线程A从ThreadB.join()操作成功返回。这意味着:线程A执行操作ThreadB.join()并成功返回后,线程B的任意操作都将对线程A可见。

    举个栗子:

    image-20210513075624617

    image-20210513075716862

  9. Volatile内存语义总结:

    image-20210513090021565

  10. Volatile内存语义实现-JMM针对编译器指定的重排序规则表

    2

  11. Volatile内存语义实现

    image-20210513091727487

    image-20210513091737422

  12. 锁的释放获取建立的happens-before原则

    1

  13. JMM的内存可见性保证

    image-20210513093736642

posted @ 2022-02-21 22:16  隐风  阅读(39)  评论(0)    收藏  举报