JMM 是什么?
Java 内存模型的主要目的是定义程序中各种变量 (这里讨论的变量是指线程共享变量) 的访问规则, 即关注在虚拟机中把变量值存储到内存和从内存中取出变量值这样的底层细节.
为了获得更好的执行效能, Java 内存模型并没有限制执行引擎使用处理器的特定寄存器或缓存来和主内存进行交互, 也没有限制即时编译器是否要进行调整代码执行顺序这类优化措施.
Java 内存模型规定:
:::info
所有的变量都存储在主内存.
每个线程有自己的工作内存, 线程的工作内存中保存了被该线程使用的变量的主内存副本.
线程对变量的所有操作 (读取, 赋值等) 都必须在工作内存中进行, 而不能直接读写主内存中的数据.
不同的线程之间也无法直接访问对方工作内存中的变量, 线程间变量值的传递均需要通过主内存来完成.
根据 JVM 规范的约定, volatile 变量依然有工作内存的拷贝, 但是由于它特殊的操作顺序性规定, 所以看起来如同直接在主内存中读写访问一般, 因此这里的描述对于 volatile 也并不存在例外.
:::

JMM vs. 操作系统内存模型
Java 内存模型和操作系统的内存模型有很高的相似性.
或者说, 先有了物理机的内存模型, 才有了虚拟机的内存模型.

可见性 原子性 有序性
可见性
可见性, 是指线程之间的可见性, 一个线程修改的状态对另一个线程是可见的, 也就是一个线程修改的结果, 另一个线程马上就能看到.
可见性是一种复杂的属性, 因为可见性中的错误总是会违背我们的直觉.
通常, 我们无法确保执行读操作的线程能适时地看到其他线程写入的值, 有时甚至是根本不可能的事情.
为了确保多个线程之间对内存写入操作的可见性, 必须使用同步机制.
volatile, synchronized, final, Lock 可以保证可见性
原子性
JMM 只保证了基本读取和赋值的原子性操作, 其他的操作均不保证原子性.
synchronized, Lock, CAS 可以保证原子性.
有序性
Java 语言提供了 volatile 和 synchronized 两个关键字来保证线程之间操作的有序性.
volatile 是因为其本身包含 “禁止指令重排序” 的语义.
synchronized 是由 “一个变量在同一个时刻只允许一个线程对其进行 lock 操作” 这条规则获得的, 此规则决定了持有同一个对象锁的两个同步代码块只能串行执行.
此外, JMM 具备一些天生的有序性规则, 不需要任何同步手段就能够保证有序性, 这个规则称为 Happen-Before 原则.
volatile, synchronized, Lock, Happen-Before 可以保证有序性.
64 位操作问题
JMM 要求, 变量的读取和写入必须是原子操作, 但是对非 volatile 类型的 long 和 double 变量, JVM 允许将 64 位的读操作或写操作分解为两个 32 位的操作.
当读取一个非 volatile 类型的 64 位变量, 如果对该变量的读操作和写操作在不同的线程中执行, 那么很可能会读到某个值的高 32 位和另一个值的低 32 位.
Happen-Before 原则

