Java 内存模型的基础
时间:2018年11月12日
1. 线程之间的通信方式
同步是指程序中用于控制不同线程间操作发生相对顺序的机制。
线程之间的通信方式有两种:
-
共享内存(Java 并发采用这种模型)
特点:隐式通信,显式同步。
-
消息传递
特点:显式通信,隐式同步。
2. 重排序
在 Java 代码的执行过程中,为了提高其执行性能,编译器 和 处理器 常常会对指令进行重排序。
重排序可分为 2 类 3 种:
-
编译器优化的重排序,编译器在不改变 单线程 程序语义的前提下,可以重新安排程序语句的执行顺序。
-
指令级并行的重排序,不存在 数据依赖 的前提下,处理器可以改变语句对应的机器指令的执行顺序。
-
内存系统的重排序。由于处理器使用缓存,这使得加载和存储操作看上去是无序的。
1 属于编译器重排序,2 和 3 属于处理器重排序。
2.1 数据依赖
前面提到指令级重排序是在 2 个操作指令之间不存在 数据依赖 的前提下进行的。那么,怎么样才算数据依赖呢?
如果,两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在着数据依赖性。
数据依赖分为下面 3 种类型:
名称 | 代码示例 | 说明 |
---|---|---|
先写后读 | a = 1; b = a; |
写一个变量之后,再读这个位置 |
先写后写 | a = 1; a = 2; |
写一个变量之后,再写这个变量 |
先读后写 | b = a; a = 1; |
读一个变量之后,再写这个变量 |
2.2 as-if-serial 语义
不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果都不会发生变化。
为了遵守 as-if-serial 语义,编译器和处理器不会对存在 数据依赖 关系的操作做重排序。
3. 顺序一致性模型
顺序一致性模型,是为程序员提供一个可见性的保证。
顺序一致性内存模型有两大特征:
-
一个线程中的所有操作必须按照程序的顺序来执行;
-
(不管程序是否同步)所有线程都只能看到一个单一的操作执行顺序,即在顺序一致性模型中,每个操作都必须是原子执行且操作的结果对所有的线程都立即可见。
4. happens-before 规则
-
程序顺序规则:一个线程中的每个操作,happens-before 于该线程中的任意后续操作。
-
监视器锁规则:对一个锁的解锁,happens-before 于随后对这个锁的加锁。
-
volatile 变量规则:对一个 volatile 域的写,happens-before 于任意后续对这个 volatile 的读。
-
传递性:如果 A happens-before B,B happens-before C,那么 A happens-before C。
-
start() 规则:如果线程 A 执行操作 ThreadB.start(),那么 A 线程的 ThreadB.start() 操作 happens-before 于线程 B 中的任意操作。
-
join() 规则:如果线程 A 执行操作 ThreadB.join() 并成功返回,那么线程 B 中的任意操作 happens-before 于线程 A 从 ThreadB.join() 操作成功返回。