Java中内存模型的定义
Java中内存模型的定义在Java语言规范的第17章第4小结,原文定义如下:
A memory model describes, given a program and an execution trace of that program, whether the execution trace is a legal execution of the program. The Java programming language memory model works by examining each read in an execution trace and checking that the write observed by that read is valid according to certain rules.
The memory model describes possible behaviors of a program. An implementation is free to produce any code it likes, as long as all resulting executions of a program produce a result that can be predicted by the memory model.
中文描述如下:
对于一个特定的程序和这个程序的一个执行流程,内存模型描述了这个执行流程是不是合法的。Java编程语言内存模型通过检测一个执行流程中的读然后根据特定的规则检查被读观测的写是否有效。
内存模型描述了一个程序的可能行为。一个实现可以生成它所喜欢的任意代码,只要程序执行所产生的结果是可以被这个内存模型预测的。
例1. 没有正确同步的程序可能产生令人惊讶的行为
比如表A中的程序流程。这个程序使用了本地变量r1和r2,共享变量A和B。一开始,A == B == 0。
表A. 指令重排导致的令人惊讶的结果
Thread 1 | Thread 2 |
---|---|
1: r2 = A; | 3: r1 = B; |
2: B = 1; | 4: A = 2; |
看起来r2 == 2和r1 == 1这个结果是不可能的。直观上看,不是指令1开始执行就是指令3开始执行。如果指令1开始执行,它不能看到指令4中的写操作。如果指令3开始执行,它不能看到指令2中的写操作。
如果这个结果(r2 == 2和r1 == 1)是成立的话,指令执行的顺序应该是:4 -> 1 -> 2 -> 3 。从表面上看,这是荒谬的。
然而,编译器允许重排每一个隔离线程中的指令,只要不影响那个线程的执行。如果指令1和指令2发生了重排,如表B所示,就可以很容易的看到结果(r2 == 2和r1 == 1)是怎么来的。
Thread 1 | Thread 2 |
---|---|
2: B = 1; | 3: r1 = B; |
1: r2 = A; | 4: A = 2; |
对于一些程序员来说,这个行为看起来是错误的。然而,值得注意的是代码没有被正确的同步:
- 每一个线程中都有一个写操作
- 另外一个线程中对同一个变量的读操作
- 读和写操作没有用同步排序
这是一个数据竞争的例子。当代码中存在数据竞争的时候,经常会导致反直觉的结果。
内存模型决定了在程序中的每个点可以读取什么值。每个隔离线程的行为受线程语义的控制,而内存模型决定了每一个读取的值。我们称这种情况下,程序遵循线程内语义。线程内语义是单线程程序的语义,线程内读取值的行为是完全可以预测的。为了判断执行中线程t的行为是否合法,我们简单地在单线程上下文中执行。
每一次线程t的执行产生了线程间的行为,必须遵照程序的执行顺序。如果是读的行为,线程t随后的执行将会使用这个值。
17.4.1 共享变量
在线程间共享的内存被称为共享内存或堆内存。
所有的实例字段、静态字段喝数组元素被存储在堆内存中。在这一章中,我们使用变量这一术语来来指代字段和数组元素。
本地变量、标准的方法参数和异常处理参数永远不会在线程间共享并且不受内存模型影响。
对同一个变量的两种访问方式(读或写)会发生冲突,如果其中一个访问方式是写。
由上述语句可以看出,内存模型实际上影响了线程间共享变量的访问方式。
17.4.2 行为
线程间的行为是一个线程产生的可以被另外一个线程监测到或直接被另外一个线程影响的行为。由如下几种程序产生的线程间行为:
- Read(normal,or non-volatile).读一个变量
- Write(normal,or non-volatile).写一个变量
- 同步行为,如下:
- Volatile read.
- Volatile write.
- Lock.
- Unlock.
- 线程的第一个和最后一个行为
- 开启一个线程或检测一个线程已经终止的行为
- 外部行为。
由于规范太长,在这个就不继续翻译了,详细的内容请参见原文。
很多人第一次看到内存模型自然而然的会认为内存模型是描述了对象在内存中的分布,其实并不是这个样子的。JVM运行时所占的内存区域在Java虚拟机规范的第2章第5小结中被定义,被称为运行时数据区。
Java虚拟机定义了各种运行时数据区用于程序的执行。一些数据区在Java虚拟机启动的时候被创建,在Java虚拟机退出的时候被销毁。其它的数据区是和线程相关的。线程相关的数据区当线程被创建时创建,当线程退出时销毁。
Java内存模型其实是保证了多线程环境下内存访问的一致性,而运行时数据区描述了Java虚拟机运行时所需要的空间划分。
有了上面的认知以后,再看下面这篇文章就能理清作者的思路,否则会觉得很乱。
全面理解Java内存模型(JMM)及volatile关键字
内存屏障相关的内容也应该了解一些:
JMM——volatile与内存屏障
Java虚拟机规范和Java语言规范的下载地址:
Java Language and Virtual Machine Specifications