当前位置: 首页 > 面试题库 >

Java最终字段的值的“最新”保证是否扩展到间接引用?

杜英范
2023-03-14
问题内容

Java语言规范在17.5节中定义了final字段的语义:

最终字段的使用模型很简单。在该对象的构造函数中设置该对象的最终字段。不要在对象的构造函数完成之前,在另一个线程可以看到它的地方编写对正在构造的对象的引用。如果执行此操作,则当另一个线程看到该对象时,该线程将始终看到该对象的最终字段的正确构造版本。它还将查看那些最终字段引用的任何对象或数组的版本,这些版本至少与最终字段一样。

我的问题是-“最新”保证是否扩展到嵌套数组和嵌套对象的内容?

简而言之:如果一个线程将可变对象图分配给对象的最终字段,并且该对象图从未更新,那么所有线程都可以通过最终字段安全地读取该对象图吗?

一个示例场景:

  1. 线程A构造一个ArrayLists的HashMap,然后将HashMap分配给类“ MyClass”的实例中的最终字段“ myFinal”
  2. 线程B看到对MyClass实例的(非同步)引用,并读取“ myFinal”,并访问并读取ArrayList之一的内容

在这种情况下,是否保证线程B看到的ArrayList成员至少与MyClass的构造函数完成时的成员保持最新?

我正在寻找Java内存模型和语言规范的语义的说明,而不是同步之类的替代解决方案。我的梦想答案是是或否,并参考了相关文本。

更新:

  • 我对Java 1.5及更高版本的语义(即通过JSR 133引入的更新的Java内存模型)感兴趣。在此更新中引入了对最终字段的“最新”保证。

问题答案:

在这种情况下,是否保证线程B看到的ArrayList成员至少与MyClass的构造函数完成时的成员保持最新?

对,他们是。

线程在首次遇到引用时需要读取内存。因为哈希映射是构造而成的,所以其中的所有条目都是全新的,因此对对象的引用up-to-date是指构造函数完成后的对象。

初次遇到之后,将应用通常的可见性规则。因此,当其他线程更改最终引用中的非最终字段时,其他线程可能看不到该更改,但仍会看到构造函数发出的引用。

实际上,这意味着如果不在构造函数之后修改最终的哈希映射,则其内容对于所有线程都是常量。

编辑

我知道我以前曾经见过这种保证。

这是本文中描述JSR 133
的一段有趣的内容

初始化安全

新的JMM还寻求提供初始化安全性的新保证-
只要正确构造了一个对象(这意味着在构造函数完成之前不会发布对该对象的引用),那么所有线程都将看到它的最终字段是在其构造函数中设置的,无论是否使用同步将引用从一个线程传递到另一个线程。此外,也可以保证通过适当构造的对象的最终字段(例如,由最终字段引用的对象的字段)可以到达的任何变量也对其他线程可见。这意味着,除了其他线程可以看到的正确值之外,如果最终字段还包含对LinkedList的引用,而且在构建时该LinkedList的内容对于其他线程也是可见的,而无需同步。结果显着增强了final的含义-
可以安全地访问final字段而无需同步,并且编译器可以假定final字段不会更改,因此可以优化多次提取。



 类似资料:
  • 在可能的副本上: 此线程不是在询问如何扩展类。它问为什么一个声明为的类可能会扩展另一个类。 从该线程: <code>final</code>类只是一个不能扩展的类。 但是,我有一个帮助程序类,我声明它是,并了另一个类: Eclipse没有检测到任何错误。我已经测试了这个类,并且PDF是成功生成的,没有错误。 为什么我能够课程,而理论上我不应该延长? (如果重要的话,我正在使用Java7。)

  • 问题内容: 让我们从一个简单的测试用例开始: 任何人都在乎猜测什么将作为输出打印(在底部显示,以免立即破坏惊喜)。 问题是: 为什么原始和包装的整数表现不同? 为什么反射访问与直接访问返回不同的结果? 最困扰我的人-为什么String表现得像原始的而不是像? 结果(java 1.5): 问题答案: 内联编译时常量(在javac编译时)。参见JLS,尤其是15.28定义了常量表达式,而13.4.9讨

  • 问题内容: 使用JavaDoc,如何在类中引用最终静态字段的值? 我希望在此示例中将其替换为field的值。 问题答案: 你的意思是?

  • 我需要在我的应用程序中创建100个或更多的静态最终常数,我可以通过以下两种方式实现这一点: 创建一个简单的java并在该类中创建字段 创建接口时应将所有变量放入其中,因为接口中的所有字段都是隐式的 在上述方法中,我有以下问题: 哪种方法是实现这一目标的正确方法

  • 问题内容: 最近,我发现匿名类和lambda表达式之间有细微的区别: 通常,lambda与匿名类等效。甚至我的Eclipse IDE都具有重构功能,可以将转换为lambda(变得完全像)并转换为匿名类(变得完全像)。但是lambda给了我一个编译错误,而匿名类却可以完美地编译。错误消息如下所示: 所以问题是:为什么会有这种差异? 问题答案: 这与处理前向引用的JLS#8.3.3有关。特别是,如果使

  • 问题内容: 可以在Java中将接口声明为final吗? 问题答案: 接口是100%抽象的,创建接口实例的唯一方法是实例化实现该接口的类。允许接口完全没有意义。 编辑 问题不像我最初想到的那样彻底。最终接口是不能由其他接口扩展但可以表面上实现的接口。 我可以想到最终类和最终接口之间的区别。扩展类可能会损害其完整性,因为它包含某些状态。扩展接口只是增加了操作,并且不会损害实现的完整性,因为接口本身是无