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

关于在对象的构造函数完成之前对对象的引用

申屠健
2023-03-14
问题内容

你们每个人都知道 JMM的 这一功能,有时对对象的引用可能 此对象的构造函数完成 之前 获得值。

在JLS7中,第4页。17.5

最终的字段语义
我们还可以阅读:

final字段的使用模型很简单:final在对象的构造函数中设置对象的字段;
并且不要在对象的构造函数完成之前,在另一个线程可以看到它的地方编写对正在构造的对象的引用
。如果执行此操作,则当另一个线程看到该对象时,该线程将始终看到该对象的final字段的正确构造版本。(1)

在紧接着的JLS中,下面的示例演示了如何确保 不最终 字段的初始化(示例17.5-1.1) (2)

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; // guaranteed to see 3 
           int j = f.y; // could see 0 
       } 
    } 
}

此外,格雷先生在这个问题解答中写道:

如果将字段标记为,final则保证构造函数作为构造函数的一部分完成初始化。否则,在使用锁之前,您将必须同步锁。(3)

因此,问题是:

1)根据声明(1),我们应该避免在 不可变 对象的构造函数完成之前共享对 不可变 对象的引用

2)根据JLS给出的示例(2)和结论(3),看来,我们可以在 不可变 对象的构造函数完成 之前 (即,当其所有字段都 为时)
安全地共享对 不可变 对象的引用final

没有矛盾吗?

EDIT-1 :我的意思是。如果我们以这种方式在示例中修改类,则该字段y也将是final(2):

class FinalFieldExample { 
    final int x; 
    final int y; 
    ...

因此在reader()方法上将保证:

if (f != null) { 
int i = f.x; // guaranteed to see 3
int j = f.y; // guaranteed to see 4, isn't it???

如果是这样,为什么f当对象的所有字段f都是final 时,为什么要避免在对象的构造函数完成之前编写对对象的引用(根据(1))?


问题答案:

(在有关构造函数和对象发布的JLS中)没有矛盾吗?

我相信这些是不矛盾的稍微不同的问题。

JLS引用是关于将对象引用存储在构造函数完成之前 其他线程
可以看到它的地方。例如,在构造函数中,您不应将对象放入static其他线程使用的字段中,也不应分叉线程。

  public class FinalFieldExample {
      public FinalFieldExample() {
         ...
         // very bad idea because the constructor may not have finished
         FinalFieldExample.f = this;
         ...
      }
  }

您也不应该在构造函数中启动线程:

  // obviously we should implement Runnable here
  public class MyThread extends Thread {
      public MyThread() {
         ...
         // very bad idea because the constructor may not have finished
         this.start();
      }
  }

即使您所有的字段都final在一个类中,在构造函数完成之前将对象的引用共享给另一个线程也不能保证在其他线程开始使用该对象时已经设置了这些字段。

我的答案是关于在构造函数完成后使用不同步的对象。尽管在构造函数,缺乏同步性以及编译器对操作的重新排序方面相似,但这是一个略有不同的问题。

在JLS 17.5-1中,它们 不在 构造函数内部分配静态字段。他们用另一种静态方法分配静态字段:

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

这是关键的区别。



 类似资料:
  • 我有一个如下所示的构造函数。 使用Mockito,我无法初始化所有3个对象accountDetailsRestClient、constantMapService和calculationMap。如果accountDetailsRestClient不为null,则constantMapService为null,反之亦然。 Mockito测试类: 但是accountDetailsRestClient现在

  • 对于我的Java应用程序,我尝试使用ScalaCheck编写一些基于属性的单元测试。为此,我需要生成器,但我能找到的所有教程都使用带有参数的构造函数来生成对象。我需要生成的对象没有构造函数参数,我无法添加这样的构造函数,因为它来自外部库。 我现在有以下内容(Jwt声明来自软件包组织.jose4j.jwt): 关于如何编写我的生成器,有什么建议吗?我对Scala一无所知,所以如果我犯了一个“明显”的

  • 问题内容: 如果在完成调用期间保存对当前对象的引用,会发生什么情况?例如: 对象是否被垃圾收集?稍后尝试访问时会发生什么? 问题答案: 该对象不是垃圾收集的。这就是所谓的“对象复活”。 您必须注意这一点,一旦终结器被称为gc,它将不会再次调用它,在.NET等某些环境中,您可以重新注册终结器,但是我不确定Java

  • 问题内容: Goetz的Java Concurrency in Practice ,第41页,提到了在构造过程中如何逃避引用。“请勿执行此操作”示例: 这是通过引用封闭实例的事实进行的“转义” 。可以通过使用静态工厂方法(首先构造普通对象,然后注册侦听器)而不是公共构造函数(完成所有工作)来解决这种情况。这本书继续说: 从其构造函数中发布对象可能会发布不完整构造的对象。这是真实的 ,即使是公布在构

  • 问题内容: 这是我无法完成的考试问题。 如何通过仅在MyClass构造函数中编辑代码来获取以下Java代码以打印false? 不允许您覆盖equals方法或更改main方法中的任何代码。该代码必须在程序不崩溃的情况下运行。 根据我的研究,实例化类时不能将Java对象引用设置为null。所以我正式陷入了困境。 问题答案: 太难了! 或Paul Boddington的简化版本: 或是AJ Neufel

  • 这是一道我无法完成的试题。 如何仅通过编辑MyClass构造函数中的代码来让下面的java代码打印false? 不允许重写equals方法,也不允许更改main方法中的任何代码。代码必须在程序不崩溃的情况下运行。 根据我的研究,在实例化类时,不能将Java对象引用设置为null。所以我正式被难倒了。