About reference to object before object's constructor is finished

核能气质少年 提交于 2019-11-28 18:54:38

Isn't there some contradiction [in the JLS around constructors and object publishing]?

I believe these are slightly different issues that are not contradictory.

The JLS reference is taking about storing an object reference in a place where other threads can see it before the constructor is finished. For example, in a constructor, you should not put an object into a static field that is used by other threads nor should you fork a thread.

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

You shouldn't start the thread in a construtor either:

  // 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();
      }
  }

Even if all of your fields are final in a class, sharing the reference to the object to another thread before the constructor finishes cannot guarantee that the fields have been set by the time the other threads start using the object.

My answer was talking about using an object without synchronization after the constructor had finished. It's a slightly different question although similar with regards to constructors, lack of synchronization, and reordering of operations by the compiler.

In JLS 17.5-1 they don't assign a static field inside of the constructor. They assign the static field in another static method:

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

This is the critical difference.

In the full example

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
        } 
    } 
}

As you can see, f is not set until after the constructor returns. This means f.x is safe because it is final AND the constructor has returned.

In the following example, neither value is guarenteed to be set.

class FinalFieldExample { 
    final int x;
    int y; 
    static FinalFieldExample f;

    public FinalFieldExample() {
        x = 3; 
        y = 4; 
        f = this; // assign before finished.
    } 

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

    static void reader() {
        if (f != null) {
            int i = f.x;  // not guaranteed to see 3  
            int j = f.y;  // could see 0
        } 
    } 
}

According to statement (1) we should avoid sharing reference to immutable object before its constructor is finished

You should not allow a reference to an object escape before it is constructed for a number of reason (immutable or other wise) e.g. the object might throw an Exception after you have store the object.

According to JLS's given example (2) and conclusion (3) it seems, that we can safely share reference to immutable object, i.e. when all its fields are final.

You can safely share a reference to an immutable object between threads after the object has been constructed.

Note: you can see the value of an immutable field before it is set in a method called by a constructor.

Construct exit plays an important role here; the JLS says "A freeze action on final field f of o takes place when c exits". Publishing the reference before/after constructor exit are very different.

Informally

1 constructor enter{

2   assign final field

3   publish this

4 }constructor exit

5 publish the newly constructed object

[2] cannot be reordered beyond constructor exit. so [2] cannot be reordered after [5].

but [2] can be reordered after [3].

Statement 1) does not say what you think it does. If anything, I would rephrase your statement:

1) According to statement (1) we should avoid sharing reference to immutable object before its constructor is finished

to read

1) According to statement (1) we should avoid sharing reference to mutable object before its constructor is finished

where what I mean by mutable is an object that has ANY non-final fields or final references to mutable objects. (have to admit I'm not 100% that you need to worry about final references to mutable objects, but I think I'm right...)


To put it another way, you should distinguish between:

  • final fields (immutable parts of a possibly immutable object)
  • non-final fields who have to be initialized before anyone interacts with this object
  • non-final fields that do not have to be initialized before anyone interacts with this object

The second one is the problem spot.

So, you can share references to immutable objects (all fields are final), but you need to use caution with objects that have non-final fields that HAVE to be initialized before the object can be used by anyone.

In other words, for the edited JLS example you posted where both fields are final, int j = f.y; is guaranteed to be final. But what that means is that you do NOT need to avoid writing the reference to object f, because it'll always be in a correctly initialized state before anyone could see it. You do not need to worry about it, the JVM does.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!