内存模型
在《Concurrent-Notes》中我们详细讨论了并发编程中的内存模型相关内容。
Java 内存模型(Java Memory Model, JMM)
由于现代计算机处理器与存储设备的运算速度存在几个数量级的差异,所以现代计算机都会在处理器与主内存之间加上高速缓存作为缓冲:将处理器计算所需数据复制到高速缓存,处理器直接从高速缓存中获取数据计算,同时处理器将计算结果放入缓存,再由缓存同步至主内存。
Java 虚拟机为了达到一次编译,到处运行的目的,也有自己的内存模型,即 Java 内存模型(JMM)。Java 内存模型着眼于描述 Java 中的线程是如何与内存进行交互,以及单线程中代码执行的顺序等,并提供了一系列基础的并发语义原则。JMM 屏蔽了各种操作系统和硬件的内存访问规则,是计算机内存模型的一种逻辑抽象。它规定所有的变量都必须存在主内存中,每个 Java 线程都有自己的工作内存,工作内存中存放了所需变量的副本,Java 线程对变量的操作必须在工作内存中,而不能直接操作主内存。
最早的 Java 内存模型于 1995 年提出,致力于解决不同处理器/操作系统中线程交互/同步的问题,规定和指引 Java 程序在不同的内存架构、CPU 和操作系统间有确定性地行为。在 Java 5 版本之前,JMM 并不完善,彼时多线程往往会在共享内存中读取到很多奇怪的数据;譬如,某个线程无法看到其他线程对共享变量写入的值,或者因为指令重排序的问题,某个线程可能看到其他线程奇怪的操作步骤。
happens-before
Java 内存模型具备一些先天的有序性,即不需要通过任何手段就能够得到保证的有序性,这个通常也称为 happens-before 原则。如果两个操作的执行次序无法从 happens-before 原则推导出来,那么它们就不能保证它们的有序性,虚拟机可以随意地对它们进行重排序。
Java 内存模型对一个线程所做的变动能被其它线程可见提供了保证,它们之间是先行发生(happens-before)关系。
- 程序次序规则:线程内的代码能够按先后顺序执行。
- 对于同一个锁,一个解锁操作一定要发生在时间上后发生的另一个锁定操作之前,也叫做管程锁定规则。
- 前一个对 volatile 的写操作在后一个 volatile 的读操作之前,也叫 volatile 变量规则。
- 一个线程内的任何操作必需在这个线程的 start() 调用之后,也叫作线程启动规则。
- 一个线程的所有操作都会在线程终止之前,线程终止规则。
- 一个对象的终结操作必需在这个对象构造完成之后,也叫对象终结规则。
对于程序次序规则来说,就是一段程序代码的执行在单个线程中看起来是有序的。注意,虽然这条规则中提到“书写在前面的操作先行发生于书写在后面的操作”,这个应该是程序看起来执行的顺序是按照代码顺序执行的,因为虚拟机可能会对程序代码进行指令重排序。虽然进行重排序,但是最终执行的结果是与程序顺序执行的结果一致的,它只会对不存在数据依赖性的指令进行重排序。因此,在单个线程中,程序执行看起来是有序执行的,这一点要注意理解。事实上,这个规则是用来保证程序在单线程中执行结果的正确性,但无法保证程序在多线程中执行的正确性。