- 线程之间以何种机制来交换信息
- 主要方式
- 共享内存(隐式通信)
- 消息传递(显式通信)
- 程序中用于控制不同线程操作发生的相对顺序
- 同步方式
- 共享内存(显式同步)
- 消息传递(隐式同步)
共享变量、局部变量
-
编译器优化的重排序
-
指令级别的并行重排序
两个指令之间不存在数据依赖即可进行指令的重排序
-
内存系统的重排序
-
as-if-serial
不管怎么重排序,单线程程序的执行结果不可被改变。编译器、runtime和处理器必须遵守 as-if-serial
-
happens-before
-
提供跨线程的内存可见性保证
-
JMM设计- 为程序员提供足够强的内存可见性保证
- 对编译器和处理器的限制要尽可能地放松
- 必须禁止会改变程序运行结果的指令重排序
-
如果一个操作的执行结果需要对另一个操作可见,这两个操作之间必须存在 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 执行操作
Thread.start()(启动线程 B), 那么线程 A 中的任意操作 happens-before 线程 B 中的任意操作。 - join() 规则:如果线程 A 执行操作
Thread.join()并且成功返回,那么线程 B 中的任意操作 happens-before 于线程 A 从Thread.join()操作成功返回。 - 线程中断规则:对线程 interrupt 方法的调用 happens-before 于被中断线程的代码检测到中断事件的发生。
- 对象终结规则:一个对象的初始化的完成,也就是构造函数执行的结束一定 happens-before 它的
finalize()方法
-
- 修饰代码块
- 修饰方法
-
锁的释放:获取建立的 happens-before 关系
锁除了使得临界区互斥之外,还可以让释放锁的线程向获取同一个锁的线程发送消息,建立 happens-before 关系。
public class MonitorExample { int a = 0; public synchronized void writer() { // 1 a++; // 2 } // 3 public synchronized void reader() { // 4 int i = a; // 5; } // 6; }
假设线程 A 首先执行
writer()方法,随后线程 B 执行reader()方法。按照 happens-before 规则,这个过程分为以下几步: -
锁释放的内存语义
当线程释放锁时,
JMM会把该线程对应的(所有)本地内存中的共享变量刷新到主内存中以面的代码为例,A 线程释放锁之后,共享数据示意图如下所示:
-
锁获取的语义
当线程获取锁时,
JMM会把该线程对应的(所有)本地内存置为无效,从而使得被监视器保护的临界区代码必须从主内存中读取共享变量依旧一上文代码为例,线程 B 在获取到锁之后,共享数据状态如下所示:
对单一变量的写对所有线程都是实时可见的。
-
volatile 写
当写一个volatile变量时,
JMM会把该线程对应的本地内存中的共享变量值刷新到主内存。 -
volatile 读
当读一个volatile变量时,
JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。
内存屏障是一种barrier指令类型,它导致CPU或编译器对barrier指令前后发出的内存操作执行顺序约束。也就是说,在barrier之前的内存操作保证在barrier之后的内存操作之前执行。
写一个 final 域不能重排序到构造函数外,读 final 域不能重排序到读该引用对象之前。
- 在构造函数内对一个 final 域的写入,与之后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序
- 初次读一个包含 final 域的对象的引用,与随后初次读这个 final 域,这两个操作之间不可重排序。
写 final 域的重排序规则
- 对于基本数据类型
JMM禁止编译器把 final 域的写重排序到构造函数之外- 编译器会在 final 域的写之后,构造函数 return 之前,插入一个
StoreStore屏障- 对于引用类型
- 在构造函数内对一个 final 引用对象的成员域的写入,与随后在构造函数外把该被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。(这一规则加强了 final,使得
String不再可能会变)
读 final 域的重排序规则
- 对于基本数据类型
- 在一个线程中,初次读该对象与初次读该对象包含的 final 域,
JMM禁止重排序这两个操作










