Java内存模型

(1) Java内存模型

Java内存模式是一种虚拟机规范。

Java虚拟机规范中定义了Java内存模型(Java Memory Model,JMM),用于屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的并发效果

JMM规范了Java虚拟机与计算机内存是如何协同工作的:
规定了一个线程如何和何时可以看到由其他线程修改过后的共享变量的值,
以及在必须时如何同步的访问共享变量。

JSR 133 (Java Memory Model) FAQ

本质上可以理解为 Java 内存模型规范了 JVM 如何提供按需禁用缓存和编译优化的方法。
具体来说,这些方法包括 volatile、synchronized 和 final 三个关键字,以及六项 Happens-Before 规则。

(2.2) Java内存模型解决了什么问题

Java内存模型解决了Java并发编程里的可见性有序性问题

(2.3) Java内存模型的具体规则

volatile

告诉编译器,对这个变量的读写,不能使用 CPU 缓存,必须从内存中读取或者写入。
解决可见性问题

synchronized

同步
解决原子性问题

final

不可变

Happens-Before 规则

(1)程序的顺序性规则

在一个线程中,按照程序顺序,前面的操作 Happens-Before 于后续的任意操作。

在一个线程内,按照程序代码顺序,书写在前面的操作先行发生于书写在后面的操作。准确地说,应该是控制流顺序而不是程序代码顺序,因为要考虑分支、循环等结构。

(2)volatile 变量规则

对一个 volatile 变量的写操作, Happens-Before 于后续对这个 volatile 变量的读操作。
对一个volatile变量的写操作先行发生于后面对这个变量的读操作,这里的”后面”同样是指时间上的先后顺序。

class VolatileExample {
  int x = 0;
  volatile boolean v = false;
  public void writer() {
    x = 42;
    v = true;
  }
  public void reader() {
    if (v == true) {
      // 这里x会是多少呢?
    }
  }
}

(3)传递性

如果 A Happens-Before B,且 B Happens-Before C,那么 A Happens-Before C。

(4)管程中锁的规则

对一个锁的解锁 Happens-Before 于后续对这个锁的加锁。
一个unlock操作先行发生于后面对同一个锁的lock操作。这里必须强调的是同一个锁,而”后面”是指时间上的先后顺序。

管程是一种通用的同步原语,在 Java 中指的就是 synchronized,synchronized 是 Java 里对管程的实现。


synchronized (this) { //此处自动加锁
  // x是共享变量,初始值=10
  if (this.x < 12) {
    this.x = 12; 
  }  
} //此处自动解锁

(5)线程 start()

关于线程启动的
主线程 A 启动子线程 B 后,子线程 B 能够看到主线程在启动子线程 B 前的操作。
Thread对象的start()方法先行发生于此线程的每一个动作。

Thread B = new Thread(()->{
  // 主线程调用B.start()之前
  // 所有对共享变量的修改,此处皆可见
  // 此例中,var==77
});
// 此处对共享变量var修改
var = 77;
// 主线程启动子线程
B.start();

(6)线程 join() 规则

关于线程等待的
主线程 A 等待子线程 B 完成(主线程 A 通过调用子线程 B 的 join() 方法实现),当子线程 B 完成后(主线程 A 中 join() 方法返回),主线程能够看到子线程的操作。

线程中的所有操作都先行发生于对此线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值等手段检测到线程已经终止执行。

Thread B = new Thread(()->{
  // 此处对共享变量var修改
  var = 66;
});
// 例如此处对共享变量修改,
// 则这个修改结果对线程B可见
// 主线程启动子线程
B.start();
B.join()
// 子线程所有对共享变量的修改
// 在主线程调用B.join()之后皆可见
// 此例中,var==66

再送一个
对象终结规则:一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始。

References

[1] 《深入理解Java虚拟机》 周志明
[2] Java内存模型(JMM)总结
[3] 02 | Java内存模型:看Java如何解决可见性和有序性问题
[4] Java内存模型FAQ
[5] JSR 133 (Java Memory Model) FAQ
[6] jsr133.pdf
[7] 深入理解 Java 内存模型(七)——总结
[8] Fixing the Java Memory Model, Part 2