On 25.07.2017 15:03, Jim Graham wrote:
On 7/25/17 12:53 PM, Sergey Bylokhov wrote:
In this variant the field should be volatile, and this will introduce some synchronization. Otherwise it will be possible to read non-null value in localEnv while the constructor of GraphicsEnvironment was not completed on some other thread(but the field was assigned already > > Even though the assignment is the last statement in the method?

The assignment include these operations:
 - Allocate memory
 - Initialize the object
 - Assign the object to the field.

These operation may be reordered, so some other thread can see non-null reference in the field while initialization was not complete.


In looking through the article that you sent out (only briefly) it looks like the consideration is that the initialization method run in one thread could reorder the instructions at run time so that the field is assigned before the initialization steps are done, even if the code is written with a different specific ordering.

I'm guessing that the Java Memory Model ensures that membars are placed to protect static field initialization from similar dangers?

The key feature here is initialization of the class.
The next line will be blocked while the LocalGE class will not be initialized:
    return LocalGE.INSTANCE;
And initialization will start only when this code will run for the first time.


If that is the case then I think we have a few places where we do this "manual conditional synchronization" that should probably be investigated. If I remember correctly, though, they may use the following paradigm which might not suffer from that issue:

Does it help if a local variable is used, as in:

GE env = this.localEnv
if (env == null) {
     synchronized (class) {
         if (this.localEnv == null) {
             localEnv = createGE();
         }
         env = this.localEnv;
     }
}
return env;

This ensures that the second read from the field is done in the synchronized block for the case that it was originally found to be null, though this might not help if the line "localEnv = createGE()" can reorder code from inside the call to createGE() to code outside of it. However those dangling reordered stores should be resolved before it leaves the synchronized block, no?

I think that it is possible that one thread will do:
 - create an object
 - assign the object to the localEnv.
 - Initialize the object.
And the second thread will do:
 - Read non-null value from the this.localEnv to the env
 - Check env to null
 - Return non-initialized object.

In any case, I can see that the code used in the webrev will eventually have no dead code at all in the final compiled version of the method whereas this version would minimally at least have a null check that must be executed even though it would be vestigial once the initialization was done...

+1 for the inner class version - it's the most straightforward way to do this optimally...
--
Best regards, Sergey.

Reply via email to