JMM
Java内存模型定义了多线程之间共享变量的可见性以及如何在需要的时候对共享变量进行同步。
Java内存模型定义了volatile和synchronized的行为,更重要的是保证了同步的java程序在所有的处理器架构下面都能正确的运行。
并发编程模型的两个关键问题
线程之间如何通信和同步。
如何通信
- 共享内存
- 消息传递
如何同步
- 显式
- 隐式
在共享内存并发模型中,同步是显式进行的。程序员必须显式指定某个方法或某段代码需要在线程之间互斥执行。在消息传递的并发模型里,由于消息的发送必须在消息的接受之前,因此同步是隐式进行的。
JMM的抽象结构
线程之间的共享变量存储在主内存,每个线程都有一个私有的本地内存,本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存、写缓冲区、寄存器以及其他的硬件和编译器优化。
happens-before
happens-before规则是JMM为了保证程序的正确结果。
重排序
为了提高性能,需要对指令进行重排序。重排序分类:
- 编译器
- 处理器
- 指令级并行重排序
- 内存系统重排序
happens-before
happens-before是JMM最核心的概念。
一个happens-before规则对应于一个或多个编译器和处理器重排序规则。
定义
1.如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。
2.两个操作之间存在happens-before关系,并不意味着Java平台的具体实现必须按照happens-before关系执行的顺序来执行。
定义1是JMM对程序员的承诺;定义2是JMM对编译器和处理器重排序的约束原则。
happens-before和as-if-serial是一回事,都有两重意思。从程序员的角度来说,好像是顺序执行的,但是只要不破坏结果,处理器和编译器怎么优化都可以
- as-is-serial语义保证
单线程
内程序的执行结果不变,happens-before语义保证正确同步的多线程程序
执行结果不变。 - as-is-serial语义给程序员一个幻境:单线程程序是按照程序的顺序来执行的。happens-before关系给编写正确同步的程序员一个幻境:正确同步的多线程程序是按照happens-before指定的顺序来执行的。
happens-before规则
- 程序顺序规则
- 监视器锁规则
- volatile变量规则
- 传递性
- start()规则:如果线程A执行操作ThreatB.start(),那么A线程的ThreadB.start()操作happens-before于B线程中的任意操作。
- join()规则:线程A执行ThreadB.join()并成功返回,那么线程B中任意操作happens-before于线程A从ThreadB.join()操作成功返回。
重排序
happens-before依赖于重排序的实现。一个happens-before对应多个重排序。
1. 数据依赖性
数据依赖的操作不可重排序
2. as-if-serial语义
不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不能被改变。编译器和处理器不会对存在数据依赖的操作重排序
顺序一致性内存模型
顺序一致行内存模型是一个理论参考模型,在设计的时候,处理器的内存模型和编程语言的内存模型都会以顺序一致性内存模型作为参照
特性
- JMM对正确同步的多线程程序的内存一致性做了如下保证:如果程序是正确同步的,程序的执行将具有顺序一致性。即程序的执行结果与该程序在顺序一致性内存模型中的执行结果相同。
- 一个线程中的所有操作必须按照程序的顺序来执行,即不进行重排序
- (不管程序是否同步)所有线程都只能看到一个单一的操作执行顺序。在顺序一致性内存模型中,每个操作都必须原子执行且立刻对所有线程可见。
volatile的内存语义
特性
- 可见性
- 原子性
内存语义
- 当写一个volatile变量时,JMM会把线程对应的本地内存中的共享变量值刷新到主内存。
- 当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来从主内存中读取共享变量。
如何实现
在指令序列中插入内存屏障来禁止特定类型的处理器排序。
比如storestore屏障可以保证volatile之前的普通写操作,在volatile变量写入之前,被刷新到主内存。
JSR-33为什么要增强volatile的语义
为了提供一种比锁更轻量级的线程之间通信的机制。
在JSR—33之前,普通变量和volatile可以重排序,会导致普通变量没有写入刷新到主内存。
锁的内存语义
内存语义
- 当线程释放锁时,JMM会把该线程对应的本地内存中的共享变量刷新到内存中。
- 当线程获取锁时,JMM会把该线程对应的本地内存置为无效。