java对象在构造过程中何时变为非空?

java对象在构造过程中何时变为非空?,java,constructor,null,order-of-execution,Java,Constructor,Null,Order Of Execution,假设您正在创建一个java对象,如下所示: SomeClass someObject = null; someObject = new SomeClass(); someObject在什么时候变为非null?是在SomeClass()构造函数运行之前还是之后 稍微澄清一下,假设另一个线程要检查someObject是否为null,而SomeClass()构造函数完成了一半,那么它是null还是非null 另外,如果像这样创建someObject,会有什么区别: SomeClass someObj

假设您正在创建一个java对象,如下所示:

SomeClass someObject = null;
someObject = new SomeClass();
someObject在什么时候变为非null?是在
SomeClass()
构造函数运行之前还是之后

稍微澄清一下,假设另一个线程要检查
someObject
是否为null,而
SomeClass()
构造函数完成了一半,那么它是null还是非null

另外,如果像这样创建
someObject
,会有什么区别:

SomeClass someObject = new SomeClass();

someObject
是否会为空?

如果另一个线程在“构造”期间检查
someObject
变量,我相信它可能(由于内存模型中的怪癖)看到一个部分初始化的对象。新的(从Java 5开始)内存模型意味着,在对象对其他线程可见之前(只要对新创建的对象的引用没有以任何其他方式从构造函数中转义),任何最终字段都应该设置为它们的值,但除此之外,没有太多的保证

基本上,如果没有适当的锁定(或静态初始化器提供的保证等),就不要共享数据。严重地,内存模型是非常棘手的,一般来说,无锁编程也是如此。尽量避免这种可能性

逻辑术语中,赋值发生在构造函数运行之后-因此,如果您从同一线程观察变量,则在构造函数调用期间它将为null。然而,正如我所说,内存模型有一些奇怪之处

编辑:出于双重检查锁定的目的,如果您的字段是volatile,并且您使用的是Java5或更高版本,那么您可以不使用这种方法。在Java5之前,内存模型还不够强大。不过,你需要把图案弄对。有关更多详细信息,请参阅有效Java,第二版,第71项

编辑:以下是我反对Aaron的内联在单个线程中可见的理由。假设我们有:

public class FooHolder
{
    public static Foo f = null;

    public static void main(String[] args)
    {
        f = new Foo();
        System.out.println(f.fWasNull);
    }
}

// Make this nested if you like, I don't believe it affects the reasoning
public class Foo
{
    public boolean fWasNull;

    public Foo()
    {
        fWasNull = FooHolder.f == null;
    }
}
我相信这将永远报告
正确
。发件人:

否则,需要三个步骤:

  • 首先,对左侧操作数求值以生成变量。如果此评估完成 突然间,任务完成了 表达式突然完成 同样的理由;右操作数为 未评估且未分配 发生
  • 否则,将计算右侧操作数。如果这 评估突然完成,然后 赋值表达式完成 出于同样的原因突然 分配发生。
否则,右边的 操作数转换为 左侧变量为 到值集转换(§5.1.13)到 适当的标准值集 (不是扩展的指数值集), 转换的结果是 存储到变量中

然后从:

两个动作可以由“发生在之前”关系排序。如果一个动作发生在另一个动作之前,那么第一个动作对第二个动作可见并在第二个动作之前排序

如果我们有两个动作x和y,我们写hb(x,y)来表示x发生在y之前

  • 如果x和y是同一线程的动作,并且x在程序顺序中位于y之前,那么hb(x,y)
  • 从一个对象的构造函数的结尾到该对象的终结器(§12.6)的开始,有一个before边
  • 如果动作x与后续动作y同步,那么我们还有hb(x,y)
  • 如果hb(x,y)和hb(y,z),那么hb(x,z)
应该注意的是,a的存在发生在两个关系之前 行动并不一定意味着它们必须以这种顺序在一个特定的时间内发生 实施如果重新排序产生的结果与合法执行一致, 这并不违法


换句话说,即使在一个线程中发生奇怪的事情也没关系,但这不能被观察到。在这种情况下,差异是可以观察到的,这就是为什么我认为它是非法的。

someObject
将是一个空指针,直到从该类型的构造函数为它分配了指针值为止。由于赋值是从右向左的,所以当构造函数仍在运行时,另一个线程可以检查
someObject
。这将在指针赋值到变量之前,因此
someObject
仍然为null。

对于第一个示例,someObject在构造函数完成后变为非null。如果从另一个线程进行检查,那么在构造函数完成后,someObject将变为非null。注意,您永远不应该从不同的线程访问未同步的对象,因此您的示例不应该以这种方式在实际代码中实现


对于第二个示例,someObject永远不会为null,因为它是在SomeClass本身被构造并且someObject被创建并用新创建的对象初始化之后构造的。线程也一样:在没有同步的情况下,不要从不同的线程访问此变量

someObject
将在构建过程中的某个时间点变为非
null
。通常有两种情况:

  • 优化器已内联构造函数
  • 构造函数不是内联的
  • 在第一种情况下,VM将执行以下代码(伪代码):

    所以在这种情况下,
    someObject
    不是
    null
    ,它指向的内存不是100%初始化的,也就是说,不是所有的构造函数代码都已运行!这就是为什么它不起作用

    在第二种情况下,构造函数中的代码将运行,引用将被传回(就像在普通的方法调用中一样),并且在所有和每个init代码运行之后,someObject将被设置为reference的值

    问题是没有办法告诉java不要分配
    someObject
    ear
    someObject = malloc(SomeClass.size);
    someObject.field = ...
    ....
    
    SomeClass tmp = new SomeClass();
    someObject = tmp;
    
    private volatile SomeClass field;
    public SomeClass getField () {
        SomeClass result = field;
        if (result == null) { // First check, no locking
            synchronized(this) {
                result = field;
                if (result == null) { // second check with locking
                    field = result = new SomeClass ();
                }
            }
        }
        return result;
    }
    
    Object o = null;
    try {
        o = new CtorTest();
    } catch (Exception e) {
        assert(o == null); // i will be null
    }
    
    class CtorTest {
        public CtorTest() {
            throw new RuntimeException("Ctor exception.");
        }
    }
    
    public class Test {
    
      private static SlowlyConstructed slowlyConstructed = null;
    
      public static void main(String[] args) {
        Thread constructor = new Thread() {
          public void run() {
            Test.slowlyConstructed = new SlowlyConstructed();
          }
        };
        Thread checker = new Thread() {
          public void run() {
            for(int i = 0; i < 10; i++) {
              System.out.println(Test.slowlyConstructed);
              try { Thread.sleep(1000); }
              catch(Exception e) {}
            }
          }
        };
    
        checker.start();
        constructor.start();
      }
    
      private static class SlowlyConstructed {
        public String s1 = "s1 is unset";
        public String s2 = "s2 is unset";
    
        public SlowlyConstructed() {
          System.out.println("Slow constructor has started");
          s1 = "s1 is set";
          try { Thread.sleep(5000); }
          catch (Exception e) {}
          s2 = "s2 is set";
          System.out.println("Slow constructor has finished");
        }
    
        public String toString() {
          return s1 + ", " + s2;
        }
      }
    }
    
    null Slow constructor has started null null null null null Slow constructor has finished s1 is set, s2 is set s1 is set, s2 is set s1 is set, s2 is set s1 is set, s2 is set