Java Memery Model

要想在日常开发中写出高性能高可靠的Java服务，熟练掌握并发编必不可少的。要想熟练掌握并发编程的技巧，就得先厘清一些和并发相关的基础理论知识，而JMM就是Java并发的核心基础。下面是是本文将描述的几个核心问题：

* JMM是什么？
* 重排序
* happens-before
* 内存屏障
* Java相关
* 总结

## JMM是什么？

Java内存模型[以下称JMM]是什么？先看看什么是内存模型.现在的多核处理器系统中[^JOA]，处理器通过多级缓存来加快数据访问，提高了性能的同时也面临了一个挑战：当两个及以上处理器同时查看同一个内存位置(比如更新同一个变量)的时候会怎么样，能看到相同的值吗？这个问题对初学者来说很抽象，观察同一个内存位置难道看到的值会不一样吗？答案是可能不一样，因为程序在运行的时候所读取的数据是来自缓存(先看一级缓存有没有，没有再去二级缓存拿，如果所有缓存都没有就会去主存加载)，可见程序使用的变量值是内存的一份拷贝，如果在程序计算的过程中主存中的值发成变化了，那么当前处理器就无法感知了。

这个时候内存模型就派上用场了。**内存模型定义了一个处理器如何才能知道其他处理器对内存(变量)的更新。(请记住这一点)** 内存模型可分为：

- 强内存模型(strong memory model)：所有核心在任何时候看到的值总是相同的。
- 弱内存模型(weak memory model)：不同的核心可能会看到不同的值，有特殊的缓存规则控制这种情况出现的时机。

强内存模型看上去对程序员来说更加友好，但是其效率低下。JMM则属于更符合真实 CPU 架构的发展趋势的弱内存模型。

![16640300606093.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228534/8b2c617e-a651-c8c3-375b-de5a4741b1ec.jpeg)

## 重排序

在描述JMM的一写重要定义前，先看看一个多线程需要面临的一个问题——重排序[Reordering]。简单的说就是程序的执行顺序可能与程序指定的顺序不一致。为了**提高执行效率**，javac、JIT和CPU指令执行都可能改变代码的执行。重排序保证的是不改变单线程的执行结果。

> CPU避免内存访问延迟最常见的技术是将指令管道化，然后尽量重排这些管道的执行以最大化利用缓存，从而把因为缓存未命中引起的延迟降到最小。编译器和CPU可以自由的重排指令以最佳的利用CPU，只要下一次循环前更新该计数器即可。

## happens-before

![happens-before.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228534/7973c372-a001-4503-eec5-7fc04e2ad06f.png)

再回到JMM，JMM的定义是通过动作(actions)的形式进行描述的，所谓动作，包括变量的读和写、管程(monitor)的加锁和释放，线程的启动和拼接(join)[^1]。如上图，我们通过Java 并发编程实践中的例子来描述怎样才能在线程B中看到线程A的执行结果——也就是happens-before法则。happens-before就是发生在某某动作之前的意思。happens-before法则包括：

- 程序次序法则：线程中的每个动作 *happens-before* 该线程中后续动作，该线程中的每个动作都按程序顺序进行。
- 管程锁[monitor]法则：对一个管程的解锁 *happens-before* 每一个后续对同一个管程的加锁。(Sychronized语义)
- volatile变量法则：对volatile字段的写入操作 *happens-before* 每一个后续的读操作。
- 线程start法则：Thread.start 调用 *happens-before* 该线程的后续所有动作。
- 线程终止法则：线程中的任何动作都 *happens-before* 线程的终止。
- 线程join法则：线程中的所有操作 *happens-before* 任何其他线程从该线程上的join()成功返回。
- 线程interrupt法则：一个线程调用另一个线程的 interrupt *happens-before* 中断线程发现中断。
- finalizer法则：一个对象的构造函数的结束 *happens-before* 这个对象的finalizer的开始。
- 传递性：如果 A *happens-before* B，B *happens-before* C，那么 A *happens-before* C。、

由类库担保的 *happens-before* 排序：

* 将一个 item 放入thread-safe容器 *happens-before* 另一个线程从该容器中获取 item。
* 锁存器(CountDownLatch)的*countDown()* *happens-before* 线程从锁存器的*await()* 中返回。
* 信号量(Semaphpre)的*release()* 一个 *permit* *happens-before* 同一个信号量的*aquire()*。
* Future中代表的任务多发生的动作 *happens-before* 另一个线成功的从*Future.get*返回。
* Excutor的*submit* *happens-before* 任务的执行。
* 一个线程到达CyclicBarrier或者Exchanger *happens-before* 其他线程的释放。

## 内存屏障

什么是内存屏障？[^memory-barrier]内存屏障是一个CPU指令。前面说过，编译器和CPU可以在保证输出结果一样的情况下对指令重排序，使性能得到优化。插入一个内存屏障，相当于告诉CPU和编译器先于这个命令的必须先执行，后于这个命令的必须后执行。内存屏障另一个作用是强制刷新CPU的缓存，这样能实现多线程的**可见性**。内存屏障指令正是*JMM happens-before*法则的底层实现。
![16640693387348.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228534/9405549e-1b6c-db38-008f-cfe2bf3ce179.png)

*内存屏障指令分类*

1. LoadLoad：禁止读和读的重排序。
2. StoreStore：禁止写和写的重排序。
3. LoadStore：禁止读和写的重排序。
4. StoreLoad：禁止写和读的重排序。

内存屏障的更多细节可参考[这里](http://ifeve.com/jmm-cookbook-mb/)。

*JDK中的内存屏蔽*

```java
public final class Unsafe {
    ...
    // loadFence=LoadLoad+LoadStore
    public native void loadFence();
    // storeFence=StoreStore+LoadStore
    public native void storeFence();
    // fullFence=loadFence+storeFence+StoreLoad
    public native void fullFence();
    ...
}
```

## Java相关
*volatile*

如果你的字段是volatile，Java内存模型将在写操作后插入一个写屏障指令，在读操作前插入一个读屏障指令。volatile的可见性使其在观察状态变化的应用中得到广泛使用。经过volatile修饰的字段，在编译之后会有一个`ACC_VOLATILE`标识。volatile的语义[^volatile]：

- 当写一个volatile变量时，JMM会把该线程对应的本地内存中的共享变量刷新到主内存。
- 当读一个volatile变量时，JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。

```java
...
public volatile int i;
    descriptor: I
    flags: (0x0041) ACC_PUBLIC, ACC_VOLATILE
...
```
*volatile实现原理*

1. 在volatile写操作的前面插入一个StoreStore屏障。保证volatile写操作不会和之前的写操作重排序。
2. 在volatile写操作的后面插入一个StoreLoad屏障。保证volatile写操作不会和之后的读操作重排序。
3. 在volatile读操作的后面插入一个LoadLoad屏障+LoadStore屏障。保证volatile读操作不会和之后的读操作、写操作重排序。

![16693572686998.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228534/4b25b2e6-9aec-1090-5ebc-3cf4183704dc.png)

## 总结

JMM是Java并发编程的理论基础，对开发安全的并发程序至关重要。如果需要了解更详细的JMM知识，[看这里。](http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html)

[^JOA]: Java性能优化实践：JVM调优策略、工具与技巧
[^1]: Java并发编程实践
[^memory-barrier]: [memory-barrier](http://ifeve.com/disruptor-memory-barrier/)
[^volatile]: [volatile 参考](http://ifeve.com/java-memory-model-4/)
[^synchronized]: Java性能权威指南