『每日一问』简单聊聊JMM/说说对JMM的了解
JMM是什么
Java Memory Model , Java内存模型,简称JMM。
Java线程之间的通信由JMM控制。JMM决定了一个线程对共享资源的写何时对另一个线程可见。从抽象的角度讲,JMM定义了线程和主内存之间的关系,每个线程都有一个本地内存1,存储了主内存中共享资源的「副本」。
JMM内存模型
上边说过了,JMM控制线程通信。那么如图线程A、B是如何通信的呢
- 线程A将自己本地内存中的共享变量刷新到主内存
- 线程B将自己本地内存中的副本置为无效,读取主内存中的共享变量值
以上两步可以看出来
- 线程之间的通信必须经过主内存。
- JMM通过控制每个线程的本地内存和主内存的交互,向我们提供内存可见性保证。
重排序
在一个程序执行的时候,为了优化程序性能,编译器和处理器常常会对指令做重排序。重排序可能会导致多线程程序出现内存可见性问题。
JMM对这两种类型重排的处理和要求:
- 对于编译器重排序:JMM会禁止特定类型的编译器重排序。 那么是哪些呢?
- 对于处理器重排序:JMM会要求Java编译器在生成指令序列的时候,插入特定类型的内存屏障(Memory Barries)来禁止特定类型的处理器重排序。
JMM作为一个语言级的内存模型,在不同的编译器和处理器上,通过禁止特定类型的编译器和处理器重排序,提供一致的内存可见性保证。
数据依赖性
如果两个操作访问同一个变量,且这两个操作中有一个是写操作,那么这两个操作之间就存在数据依赖性。
产生数据依赖性的操作有三种:
- 读后写
- 写后读
- 写后写
以上三种操作,如果顺序被重排,那么程序的结果就会被改变。
as-if-serial语义
as-if-serial(串行化)的语义是:不管编译器和处理器为了提供并行度做怎样的重排序,单线程程序的执行结果不能被改变2。
那么为了达到这个效果,编译器和处理器不会对存在数据依赖性的操作做重排序。
如图,C分别与A、B都有数据依赖关系,那么C就不会被重排在A、B任何一个之前去执行。
那么最后可能的顺序就是:
happens-before
如果一个操作happens-before另一个操作,那么第一个操作的执行结果对第二个操作可见,但是第一个操作的执行顺序不一定排在第二个之前。如果这两个操作由数据依赖关系,那么第一个操作就排在第二个之前,反之亦然。
多线程之间操作的happens-before关系的确立是依靠着代码上的正确同步。