Java 内存模型的基础

时间:2018年11月12日

  1. 线程之间的通信方式
  2. 重排序
    1. 数据依赖
    2. as-if-serial 语义
  3. 顺序一致性模型
  4. happens-before 规则

1. 线程之间的通信方式

同步是指程序中用于控制不同线程间操作发生相对顺序的机制。

线程之间的通信方式有两种:

  1. 共享内存(Java 并发采用这种模型)

    特点:隐式通信,显式同步。

  2. 消息传递

    特点:显式通信,隐式同步。

2. 重排序

在 Java 代码的执行过程中,为了提高其执行性能,编译器处理器 常常会对指令进行重排序。

重排序可分为 2 类 3 种:

  1. 编译器优化的重排序,编译器在不改变 单线程 程序语义的前提下,可以重新安排程序语句的执行顺序。

  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. 顺序一致性模型

顺序一致性模型,是为程序员提供一个可见性的保证。

顺序一致性内存模型有两大特征:

  1. 一个线程中的所有操作必须按照程序的顺序来执行;

  2. (不管程序是否同步)所有线程都只能看到一个单一的操作执行顺序,即在顺序一致性模型中,每个操作都必须是原子执行且操作的结果对所有的线程都立即可见。

4. happens-before 规则

  1. 程序顺序规则:一个线程中的每个操作,happens-before 于该线程中的任意后续操作。

  2. 监视器锁规则:对一个锁的解锁,happens-before 于随后对这个锁的加锁。

  3. volatile 变量规则:对一个 volatile 域的写,happens-before 于任意后续对这个 volatile 的读。

  4. 传递性:如果 A happens-before B,B happens-before C,那么 A happens-before C。

  5. start() 规则:如果线程 A 执行操作 ThreadB.start(),那么 A 线程的 ThreadB.start() 操作 happens-before 于线程 B 中的任意操作。

  6. join() 规则:如果线程 A 执行操作 ThreadB.join() 并成功返回,那么线程 B 中的任意操作 happens-before 于线程 A 从 ThreadB.join() 操作成功返回。

BACK