Question

Say, We have the code:

Test t = new Test();

Compile into byte code it would actually be three steps :

1. mem = allocateMem() : allocate memory for Test and save it's address.
2. construct(mem) : construct the class Test
3. t = mem : point t to the mem

Here I wonder, if construct(mem) is very slow, will JIT wait in step 2 until the mem is fully contructed ?

If it doesn't wait(async)

Then how could it guarantee the mem is fully constructed before use (Single thread)?

If it does wait(sync)

Then why double check lock (see the code below and this article) fails ?

class DB {
  private DB(){}
  private static DB instance;

  public static DB getInstance() {
    // First check
    if(instance == null ){
      synchronized(DB.class){
        // Second check
        if(instance == null) {
          instance = new Instance();
        }
      }
    }
    return instance;
  }
}

The article I referred point out that the code above will return an Instance that haven't been fully constructed.

Was it helpful?

Solution 2

Here I wonder, if construct(mem) is very slow, will JIT wait in step 2 until the mem is fully constructed ?

Assuming that you are talking about the code generated by the JIT ... then the answer is that the code does not necessarily wait at that point. It depends what comes after step 3.

Then how could it guarantee the mem is fully constructed before use (Single thread)?

The requirement is that the observed values of the variables in that thread1 are consistent with the specified semantics of the language; i.e. the "program order". The JIT is allowed to reorder the instructions if it makes no difference. Specifically, it doesn't matter if the writes to memory for some fields are delayed ... if the thread does not need to read the values of those variables from memory. (The code may not need to read them at all, it may read them from registers, or it may fetch them from level 1 or level 2 cache ....)

So the short answer to "how does it ensure it", is that it does it by emitting the instructions in an order that satisfies the actual language requirements ... not the more restrictive semantic that you posited.


I'm treating the 2nd part of your Question (about a DCL implementation) as moot.


1 - This only applies to that thread. The JLS states that there is no such requirement for consistency with respect to other threads ... unless there is a "happens-before" relationship between the write and subsequent read event.

OTHER TIPS

Check this answer I gave here on StackOverflow a long time ago for an explanation on why that DCL fails, and how to fix it.


The problem is not sync/async. The problem is something called reordering.

The JVM spec defines something called a happens-before relationship. Inside a single thread, if statement S1 appears before statement S2, then S1 happens-before S2, that is, whatever modifications S1 made to the memory are visible to S2. Note that it does not say that the statement S1 must be executed before S2. It just says that things should look as if S1 was executed before S2. For example, consider this code:

int x = 0;
int y = 0;
int z = 0;
x++;
y++;
z++;
z += x + y;
System.out.println(z);

Here, it doesn't matter the order in which the JVM executes the three increment statements. The only guarantee is that, when running z += x + y, the values of x, y, and z, must be all 1. In fact, the JVM is actually allowed to reorder statements if the reordering does not violate the happens-before relationship. The reason for this is that sometimes a little reordering can optimize your code, and you get better performance.

The drawback is that the JVM is allowed to reorder things in a way that could lead to very strange results when you use multiple threads. For example:

class Broken {
  private int value;
  private boolean initialized = false;
  public void init() {
    value = 5;
    initialized = true;
  }
  public boolean isInitialized() { return initialized; }
  public int getValue() { return value; }
}

Suppose a thread is executing this code:

while (!broken.isInitialized()) {
  Thread.sleep(1); // patiently wait...
}
System.out.println(broken.getValue());

Suppose that, now, another thread does, on the same Broken instance,

broken.init();

The JVM is allowed to reorder the code inside the init() method, by first running initialized = true, and only then setting the value to 5. If this happens, the first thread, the one waiting for the initialization, might print 0! To fix, either add synchronized to both methods, or add volatile to the initialized field.

Back to the DCL, it is possible that the initialization of the singleton gets executed in a different order. For instance:

1. mem = allocateMem() : allocate memory for Test and save it's address.
2. construct(mem) : construct the class Test
3. t = mem : point t to the mem

could become:

1. mem = allocateMem() : allocate memory for Test and save it's address.
2. t = mem : point t to the mem
3. construct(mem) : construct the class Test

because, for a single thread, both blocks are completely equivalent. That said, you can be safe that this kind of singleton initialization is completely safe for a single-threaded application. However, for multiple threads, one thread might get a reference for a partially initialized object!

To ensure a happens-before relationship between statements when you use multiple threads, you have 2 possibilities: acquiring/releasing locks and reading/writing volatile fields. To fix the DCL, you must declare the field that holds the singleton volatile. This will make sure that the initialization of the singleton (ie, running its constructor) happens-before any read of the field holding the singleton. For a somewhat detailed explanation on how volatile fixes the DCL, check the answer I linked on the top of this one.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top