原文http://renchx.com/jmm-final/
作者任春晓
对于 final 域,编译器和处理器要遵守两个重排序规则:
- 在构造函数内对一个 final 域的写,与随后把这个构造对象的引用赋值给一个变量,这两个操作之间不能重排序
- 初次读一个包含 final 域的对象的引用,与随后初次读这个 final 域,这两个操作之间不能重排序
举个例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public class FinalExample {
int i; // 普通变量
final int j; // final 变量
static FinalExample obj;
public FinalExample() {
i = 1 ; // 写普通域
j = 2 ; // 写 final 域
}
public static void writer() { // 写线程 A 执行
obj = new FinalExample();
}
public static void reader() { // 读线程 B 执行
FinalExample object = obj;
int a = object.i;
int b = object.j;
}
} |
这里假设一个线程 A 执行 writer ()方法,随后另一个线程 B 执行 reader ()方法。
写 final 域的重排序规则
在写 final 域的时候有两个规则:
- JMM 禁止编译器把 final 域的写重排序到构造函数之外
- 编译器会在 final 域的写之后,构造函数 return 之前,插入一个 StoreStore 屏障,这个屏障禁止处理器把 final 域的写重排序到构造函数之外。
分析上面的代码。
write 方法,只包含一行 obj = new FinalExample();
,但是包含两个步骤:
- 构造一个 FinalExample 对象
- 把对象的引用赋值给 obj
假设线程 B 当中读 obj 与读成员域之间没有重排序。那么执行时序可能如下:
写 final 域的重排序规则可以确保:在对象引用为任意线程可见之前,对象的 final 域已经被正确初始化过了,而普通域不具有这个保障。
读 final 域的重排序规则
读 final 域的重排序规则如下:
- 在一个线程中,初次读对象引用与初次读该对象包含的 final 域,JMM 禁止处理器重排序这两个操作(注意,这个规则仅仅针对处理器)。编译器会在读 final 域操作的前面插入一个 LoadLoad 屏障。
reader() 方法包含三个操作:
- 初次读引用变量 obj;
- 初次读引用变量 obj 指向对象的普通域 j。
- 初次读引用变量 obj 指向对象的 final 域 i。
现在我们假设写线程 A 没有发生任何重排序,那么执行时序可能是:
上面的图可以看到对普通变量 i 的读取重排序到了读对象引用之前,在读普通域时候,该域还没被写线程 A 写入,这是一个错误的读取操作。而读 final 域已经被 A 线程初始化了,这个读取操作是正确的。
读 final 域的重排序规则可以确保:在读一个对象的 final 域之前,一定会先读包含 这个 final 域的对象的引用。在这个示例程序中,如果该引用不为 null,那么引用 对象的 final 域一定已经被 A 线程初始化过了。
如果 final 域是引用类型
如果 final 域是引用类型,写 final 域的重排序规则对编译器和处理器增加了如下约束:
- 在构造函数内对一个 final 引用的对象的成员域的写入,与随后在构造函数外把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。
如下代码例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public class FinalReferenceExample {
final int [] intArray;
static FinalReferenceExample obj;
public FinalReferenceExample() {
intArray = new int [ 1 ]; // 1
intArray[ 0 ] = 1 ; // 2
}
public static void writerOne() { // A线程执行
obj = new FinalReferenceExample(); // 3
}
public static void reader() { // 写线程 B 执行
if (obj != null ) { // 4
int temp1 = obj.intArray[ 0 ]; // 5
}
}
} |
假设首先线程 A 执行 writerOne()方法,执行完后线程 B 执行reader 方法,JMM 可以确保读线程 B 至少能看到写线程 A 在构造函数中对 final 引用对象的成员域的写入。
避免对象引用在构造函数当中溢出
代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public class FinalReferenceEscapeExample {
final int i;
static FinalReferenceEscapeExample obj;
public FinalReferenceEscapeExample() {
i = 1 ; // 1
obj = this ; // 2 避免怎么做!!!
}
public static void writer() {
new FinalReferenceEscapeExample();
}
public static void reader() {
if (obj != null ) { // 3
int temp = obj.i; // 4
}
}
} |
假设一个线程 A 执行 writer()方法,另一个线程 B 执行 reader()方法。
这里的操作 2 使得对象还未完成构造前就为线程 B 可见。即使这里的操作 2 是构造函数的最后 一步,且即使在程序中操作 2 排在操作 1 后面,执行 read()方法的线程仍然可能无 法看到 final 域被初始化后的值,因为这里的操作 1 和操作 2 之间可能被重排序。
在构造函数返回前,被构造对象的引用不能为其他线程可 见,因为此时的 final 域可能还没有被初始化。在构造函数返回后,任意线程都将 保证能看到 final 域正确初始化之后的值。
相关推荐
《深入理解 Java 内存模型》这本书由程晓明编著,旨在帮助开发者深入理解和应用 JMM。 1. **内存层次结构**:Java 内存模型将内存分为堆内存、栈内存、方法区(在 Java 8 及以后版本中被元空间替代)和程序计数器等...
深入理解Java内存模型可以帮助开发者避免并发编程中常见的问题,如数据竞争、死锁和活锁等。通过合理地使用同步机制,可以编写出高效且线程安全的代码,这对于大型分布式系统和高并发应用尤为重要。 总之,Java内存...
在构造函数内对一个 final 域的写,与随后把这个构造对象的引用赋值给一个变量,这两个操作之间不能重排序 初次读一个包含 final 域的对象的引用,与随后初次读这个 final 域,这两个操作之间不能重排序 举...
Java内存模型是Java虚拟机规范中定义的一部分,它规定了Java程序中变量的读写行为,以及线程之间的交互规则。理解Java内存模型对于编写正确、高效的多线程程序至关重要。在Java 5之前,Java内存模型的描述比较模糊,...
本教程《深入理解JAVA内存模型》将带你深入探讨这一主题,尤其关注Java中的同步原语——synchronized、volatile和final。 首先,我们要了解JMM的基础结构。JMM规定了程序中各个线程如何访问和修改共享变量,包括主...
《深入理解Java虚拟机》是一本深度探讨Java虚拟机(JVM)的著作,涵盖了JVM性能调优、内存模型以及虚拟机原理等多个关键领域。本文将基于这些主题,详细阐述其中的重要知识点。 首先,我们要了解Java虚拟机(JVM)...
深入理解Java内存模型对于优化程序性能、避免并发问题至关重要。 首先,让我们看下Java虚拟机的整体结构。JVM由几个主要子系统和内存区域组成: 1. **类装载器子系统**:负责根据全限定名加载类和接口。这包括启动...
这些文档如"Java内存模型.docx"、"Java内存模型2.docx"、"深入Java核心 Java内存分配原理精讲.docx"、"java内存模型.pdf"将深入探讨这些概念,帮助开发者更深入地理解Java内存模型及其在实际编程中的应用。...
总结来说,深入理解Java内存模型能帮助我们更好地掌握Java并发编程的底层原理,编写出高效、无错的并发代码,同时也有利于进行JVM的性能优化。通过阅读"深入理解JAVA内存模型.pdf",可以系统学习和掌握这些关键知识...
深入理解Java内存模型对于编写高效、安全的并发程序至关重要。 Java内存模型规定了线程之间的共享变量如何交互,以及在什么条件下能保证一致性和可见性。它通过内存屏障、 volatile、synchronized、final关键字以及...
Java内存模型(Java Memory Model,JMM)是Java虚拟机(JVM)中的一种内存模型,它描述了程序中各个变量之间的关系,以及在实际计算机系统中将变量存储到内存和从内存中取出变量这样的底层细节。JMM允许编译器和缓存...
Java 内存模型(Java Memory Model,JMM)是 Java 平台规范的一部分,它定义了程序中各个线程如何访问和修改共享变量的规则,确保多线程环境下的正确性。JMM 主要是为了处理并发编程中可能出现的数据一致性问题,...
《深入Java内存模型》是一本面向Java开发人员的专业书籍,旨在帮助读者深入理解Java平台的内存管理和性能优化。这本书详细探讨了Java内存模型(JVM)的基础知识,以及如何利用这些知识来提升程序的效率和稳定性。...
深入理解 Java 内存模型及其内存操作规则,有助于编写出正确且高效的并发代码,避免出现如死锁、活锁、饥饿等并发问题。开发者应当熟练掌握这些知识,以便在实际开发中做出正确的设计决策,提升程序的并发性能和稳定...
Java内存模型,简称JMM(Java Memory Model),是Java虚拟机规范中定义的一个抽象概念,它描述了在多线程环境下,如何保证各个线程对共享数据的一致性视图。JMM的主要目标是定义程序中各个变量的访问规则,以及在...