1. Java 内存模型(JMM)
JMM(Java Memory Model)并不真实存在,而是和多线程相关的一组『规范』,需要每个 JVM 的实现都要遵守这样的『规范』,有了 JMM 的规范保障,并发程序运行在不同的虚拟机得到出的程序结果才是安全可靠可信赖,JMM 是 Java 并发编程的基础。
JMM 与处理器、缓存、并发、编译器有关。它解决了 CPU 多级缓存、处理器优化、指令重排等导致的结果不可预期的问题,保证不同的并发语义关键字得到相应的并发安全的数据资源保护。JMM 是 JUC 包工具类和并发关键字的原理保障。如volatile
、synchronized
、Lock
等,它们的实现原理都涉及 JMM。有了 JMM 的参与,才让各个同步工具和关键字能够发挥作用同步语义才能生效,使得我们开发出并发安全的程序。
JMM 抽象出主内存和本地内存两种:
- 主内存是实例位置所在的区域,所有的实例都存在于主内存内。比如,实例所拥有的字段即位于主内存内,主内存是所有的线程所共享的。
- 本地内存是线程所拥有的作业区,每个线程都有其专用的本地内存。地内存中存储了该线程以读/写共享变量的副本。本地内存是 JMM 的一个抽象概念,并不真实存在,它涵盖了缓存、写缓冲区、寄存器以及其他的硬件和编译器优化。
从抽象角度看,JMM 定义了线程与主内存之间的抽象关系:
- 线程之间的共享变量存储在主内存(Main Memory)中;
- 每个线程都有一个私有的本地内存(Local Memory),本地内存是 JMM 的一个抽象概念,并不真实存在,它涵盖了缓存、写缓冲区、寄存器以及其他的硬件和编译器优化。本地内存中存储了该线程以读/写共享变量的拷贝副本。
- 从更低的层次来说,主内存就是硬件的内存,而为了获取更好的运行速度,虚拟机及硬件系统可能会让工作内存优先存储于寄存器和高速缓存中。
- Java 内存模型中的线程的工作内存(working memory)是 CPU 的寄存器和高速缓存的抽象描述。而 JVM 的静态内存储模型(JVM 内存模型)只是一种对内存的物理划分而已,它只局限在内存,而且只局限在 JVM 的内存。
2. JMM 最重要的三点内容:原子性、可见性、有序性
Java 内存模型(JMM)是围绕着在并发过程中如何处理原子性、可见性和有序性这三个特征来建立的。
Atomic 变量轻量地实现了原子性,volatile 关键字能够解决可见性和有序性的问题,而 synchronized 关键字可以同时解决原子性、可见性、有序性的问题。synchronized 关键字的万能也造成了它被程序员滥用的局面。
内存屏障用于控制在特定条件下的重排序和内存可见性问题。JMM内存屏障可分为读屏障和写屏障。Java 编译器在生成字节码时,会在执行指令序列的适当位置插入内存屏障来限制处理器的重排序。
2.1 原子性
2.2 可见性
使用 volatile 可以解决可见性的问题。volatile 修饰的变量,在发生变化的时候,其它线程会立刻觉察到,然后从主存中取得更新后的值。
除了 volatile 之外,Java 还有两个关键字能实现可见性,它们是 synchronized 和 final。synchronized 也可以确保可见性,在一个线程执行完 synchronized 代码后,所有代码中对变量值的变化都能立即被其它线程所看到。
2.3 有序性
指令重排序:CPU 为了提高运行效率,可能会对编译后代码的指令做一些优化,这些优化不能保证 100% 符合你编写代码在正常编译后的顺序执行,但是一定能保证代码执行的结果和按照编写顺序执行的结果是一致的。volatile 关键字还可以禁止指令重排序。
指令重排序并不是代码重排序。我们的代码被编译后,一行代码可能会对应多条指令,所以指令重排序更为细粒度。如下图所示:
3. Happens-Before 简介
在 JMM 中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须要存在 happens-before 关系。与程序员密切相关的 happens-before 规则如下:
- 程序顺序规则:一个线程中的每个操作,happens-before 于该线程中的任意后续操作。
- 监视器锁规则:对一个锁的解锁,happens-before 于随后对这个锁的加锁。
- volatile 变量规则:对一个 volatile 域的写,happens-before 于任意后续对这个 volatile 域的读。
- 传递性:如果 A happens-before B,且 B happens-before C,那么 A happens-before C。
注意:happens-before 并不意味着前一个操作必须要在后一个操作之前执行!happens-before 仅仅要求前一个操作对后一个操作可见,且前一个操作按顺序排在第二个操作之前。