Loading

Java内存模型(JMM)

Java内存模型(JMM)

1. 计算机内存系统

计算机在执行程序时,每条指令都是在CPU中执行的,而执行指令过程中,势必会涉及到数据的读取和写入。

由于在程序运行过程中,临时数据是存放在主存(物理内存)中的,这时就存在一个问题,由于CPU执行指令的速度很快,而从内存读取和写入数据的过程与其相比速度要慢得多,因此如果任何时候对数据的操作都要通过和内存的交互来进行,会大大降低指令执行的速度。

因此CPU中就有了高速缓存。当程序在运行过程中,会将运算需要的数据从主存复制一份到CPU的高速缓存中,当CPU进行计算时就可以直接从它的高速缓存中读取和写入数据,运算结束之后,再将高速缓存中的数据刷新到主存当中去。

2. JMM内存模型

在Java虚拟机规范中试图定义一种Java内存模型(Java Memory Model,JMM)来屏蔽各个硬件平台和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果。

JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory),本地内存中存储了该线程读/写共享变量的副本。

image-20200812233547123

如果线程A与线程B之间要通信的话,必须要经历下面2个步骤。

  • 线程A把本地内存A中更新过的共享变量刷新到主内存中去。
  • 线程B到主内存中去读取线程A之前已更新过的共享变量。

3. JMM内存模型三大特性

原子性:一个操作或多个操作,要么全部执行并且执行过程中不会被任何因素打断,要么就不执行。

  • 对于基本数据类型变量的原子性操作由JUC包原子类中的AtomicInteger等来实现;
  • 对于更大范围操作的原子性,使用 synchronizedLock 来实现。

可见性:指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值

  • volatile:当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。
  • synchronizedLock:保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中,因此可以保证可见性。
  • final:被 final 关键字修饰的字段一旦初始化完成,并且没有发生 this 逃逸(其它线程通过 this 引用访问到初始化了一半的对象),也能保证对其他线程的可见性。

有序性:程序执行的顺序按照代码的先后顺序执行。

  • 指令重排序:一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的(顺序不一致,但结果一致)。重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。

    源代码 -> 编译器优化的重排 -> 指令并行的重排 -> 内存系统的重排 ->最终执行的指令

  • 保证有序性的方法

    • volatile关键字
    • 通过synchronized和Lock来保证有序性,其可以保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然也就保证了有序性;
    • Java内存模型具备一些先天的“有序性”,即不需要通过任何手段就能够得到保证的有序性,通常称为happens-before原则。在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须要存在happens-before关系。这两个操作既可以在一个线程之内,也可以在不同线程之间。happens-before的一些重要规则:
      • 程序顺序规则:一个线程内,按照代码顺序,书写在前的操作线性发生于先行在后的操作;
      • 监视器锁规则:一个unlock操作先行发生于后面对同一个锁的lock操作;
      • volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作;
      • 传递性:若操作A先行发生于B,而操作B又先行发生于C,则可以得出操作A先行发生于C。
posted @ 2020-08-13 00:06  Chiakiiii  阅读(149)  评论(0编辑  收藏  举报