Java内存模型FAQ(九)在新的Java内存模型中,final字段是如何工作的

简介:

原文:http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html 第九章
译者:Alex

一个对象的final字段值是在它的构造方法里面设置的。假设对象被正确的构造了,一旦对象被构造,在构造方法里面设置给final字段的的值在没有同步的情况下对所有其他的线程都会可见。另外,引用这些final字段的对象或数组都将会看到final字段的最新值。

对一个对象来说,被正确的构造是什么意思呢?简单来说,它意味着这个正在构造的对象的引用在构造期间没有被允许逸出。(参见安全构造技术)。换句话说,不要让其他线程在其他地方能够看见一个构造期间的对象引用。不要指派给一个静态字段,不要作为一个listener注册给其他对象等等。这些操作应该在构造方法之后完成,而不是构造方法中来完成。


class FinalFieldExample {
  final int x;
  int y;
  static FinalFieldExample f;
  public FinalFieldExample() {
    x = 3;
    y = 4;
  }

  static void writer() {
    f = new FinalFieldExample();
  }

  static void reader() {
    if (f != null) {
      int i = f.x;
      int j = f.y;
    }
  }
}


上面的类展示了final字段应该如何使用。一个正在执行reader方法的线程保证看到f.x的值为3,因为它是final字段。它不保证看到f.y的值为4,因为f.y不是final字段。如果FinalFieldExample的构造方法像这样:


public FinalFieldExample() { // bad!
  x = 3;
  y = 4;
  // bad construction - allowing this to escape
  global.obj = this;
}


那么,从global.obj中读取this的引用线程不会保证读取到的x的值为3。

能够看到字段的正确的构造值固然不错,但是,如果字段本身就是一个引用,那么,你还是希望你的代码能够看到引用所指向的这个对象(或者数组)的最新值。如果你的字段是final字段,那么这是能够保证的。因此,当一个final指针指向一个数组,你不需要担心线程能够看到引用的最新值却看不到引用所指向的数组的最新值。重复一下,这儿的“正确的”的意思是“对象构造方法结尾的最新的值”而不是“最新可用的值”。

现在,在讲了如上的这段之后,如果在一个线程构造了一个不可变对象之后(对象仅包含final字段),你希望保证这个对象被其他线程正确的查看,你仍然需要使用同步才行。例如,没有其他的方式可以保证不可变对象的引用将被第二个线程看到。使用final字段的程序应该仔细的调试,这需要深入而且仔细的理解并发在你的代码中是如何被管理的。

如果你使用JNI来改变你的final字段,这方面的行为是没有定义的。

 

原文

How do final fields work under the new JMM?

The values for an object’s final fields are set in its constructor. Assuming the object is constructed “correctly”, once an object is constructed, the values assigned to the final fields in the constructor will be visible to all other threads without synchronization. In addition, the visible values for any other object or array referenced by those final fields will be at least as up-to-date as the final fields.

What does it mean for an object to be properly constructed? It simply means that no reference to the object being constructed is allowed to “escape” during construction. (See Safe Construction Techniques for examples.)  In other words, do not place a reference to the object being constructed anywhere where another thread might be able to see it; do not assign it to a static field, do not register it as a listener with any other object, and so on. These tasks should be done after the constructor completes, not in the constructor.


class FinalFieldExample {
  final int x;
  int y;
  static FinalFieldExample f;
  public FinalFieldExample() {
    x = 3;
    y = 4;
  }

  static void writer() {
    f = new FinalFieldExample();
  }

  static void reader() {
    if (f != null) {
      int i = f.x;
      int j = f.y;
    }
  }
}


The class above is an example of how final fields should be used. A thread executing reader is guaranteed to see the value 3 for f.x, because it is final. It is not guaranteed to see the value 4 for y, because it is not final. If FinalFieldExample‘s constructor looked like this:


public FinalFieldExample() { // bad!
  x = 3;
  y = 4;
  // bad construction - allowing this to escape
  global.obj = this;
}


then threads that read the reference to this from global.obj are not guaranteed to see 3 for x.

The ability to see the correctly constructed value for the field is nice, but if the field itself is a reference, then you also want your code to see the up to date values for the object (or array) to which it points. If your field is a final field, this is also guaranteed. So, you can have a final pointer to an array and not have to worry about other threads seeing the correct values for the array reference, but incorrect values for the contents of the array. Again, by “correct” here, we mean “up to date as of the end of the object’s constructor”, not “the latest value available”.

Now, having said all of this, if, after a thread constructs an immutable object (that is, an object that only contains final fields), you want to ensure that it is seen correctly by all of the other thread, you still typically need to use synchronization. There is no other way to ensure, for example, that the reference to the immutable object will be seen by the second thread. The guarantees the program gets from final fields should be carefully tempered with a deep and careful understanding of how concurrency is managed in your code.

There is no defined behavior if you want to use JNI to change final fields. 

目录
相关文章
|
3天前
|
存储 缓存 Java
并发编程-Java内存模型到底是什么
并发编程-Java内存模型到底是什么
10 1
|
1天前
|
缓存 Java 编译器
Java内存模型深度解析
【6月更文挑战第22天】在探索Java内存模型的迷宫中,我们不仅需要理解其结构,还要揭开它运作的神秘面纱。本文将深入挖掘Java内存模型的核心概念,从硬件架构出发,到Java内存模型的设计哲学,再到并发编程中的实际应用,我们将一步步解码Java内存模型的奥秘。
|
3天前
|
Java 机器人 数据库连接
Java中的内存泄漏问题解析与应对
Java中的内存泄漏问题解析与应对
16 3
|
2天前
|
存储 缓存 Java
Java中的缓冲流提升I/O性能,通过内存缓冲区减少对硬件访问
【6月更文挑战第22天】Java中的缓冲流提升I/O性能,通过内存缓冲区减少对硬件访问。`BufferedInputStream`和`BufferedOutputStream`用于字节流,缓存数据批量读写。`BufferedReader`和`BufferedWriter`处理字符流,支持按行操作。使用后务必关闭流。
9 3
|
4天前
|
监控 算法 Java
Java虚拟机(JVM)使用多种垃圾回收算法来管理内存,以确保程序运行时不会因为内存不足而崩溃。
【6月更文挑战第20天】Java JVM运用多种GC算法,如标记-清除、复制、标记-压缩、分代收集、增量收集、并行收集和并发标记,以自动化内存管理,防止因内存耗尽导致的程序崩溃。这些算法各有优劣,适应不同的性能和资源需求。垃圾回收旨在避免手动内存管理,简化编程。当遇到内存泄漏,可以借助VisualVM、JConsole或MAT等工具监测内存、生成堆转储,分析引用链并定位泄漏源,从而解决问题。
15 4
|
4天前
|
安全 Java 开发者
深入理解Java内存模型(JMM)及其对并发编程的影响
【6月更文挑战第19天】在Java的世界中,内存模型是构建高效、线程安全应用的基石。本文将通过探讨Java内存模型(JMM)的核心概念和原理,揭示它如何影响并发编程实践。我们将从JMM的基本定义出发,逐步解析它在同步机制、可见性规则以及有序性保证方面的作用。同时,我们也将讨论JMM对现代Java开发中常见的并发模式和框架的影响。最后,文章会提供一些实际的编码建议和最佳实践,帮助开发者更好地利用JMM来设计并发程序。
|
6月前
|
Java
【零基础学Java】—final关键字与四种用法(二十九)
【零基础学Java】—final关键字与四种用法(二十九)
|
1月前
|
Java
Java中final关键字(看这篇就够了)
Java中final关键字(看这篇就够了)
33 0
|
12天前
|
Java 编译器
Java基础5-一文了解final关键字的特性、使用方法,以及实现原理(二)
Java基础5-一文了解final关键字的特性、使用方法,以及实现原理(二)
12 0
Java基础5-一文了解final关键字的特性、使用方法,以及实现原理(二)
|
12天前
|
缓存 安全 Java
Java基础5-一文了解final关键字的特性、使用方法,以及实现原理(一)
Java基础5-一文了解final关键字的特性、使用方法,以及实现原理(一)
17 0

热门文章

最新文章