[concurrency-interest] Explicitly initializing volatile fields with default values

Aleksey Shipilev aleksey.shipilev at oracle.com
Thu Dec 17 16:36:57 EST 2015


Hi there, concurrency dwellers,

I'm trying to find a counter-example/constraints against removing the
field initializers for volatile fields, when those initializers are
storing the default value. We know this improves performance, e.g.
  https://bugs.openjdk.java.net/browse/JDK-8035284

There is a gut feeling that removing these initializations is safe. But,
I have heard rumors of traces of counter-examples in OpenJDK bugtracker,
but found nothing substantial, which gives me doubts. Maybe there are
constraints I am overlooking?

First, let me show that you cannot (always) do this mechanically for all
(even non-volatile!) usages. JLS 8.3.2.3 "Restrictions on the use of
Fields during Initialization" allows this:

public class InitializerOverwrite {
  {
    x1 = 42;
    x2 = 42;
  }

  int x1;
  int x2 = 0;

  public static void main(String... args) {
    InitializerOverwrite io = new InitializerOverwrite();
    System.out.println(io.x1); // 42
    System.out.println(io.x2); // 0
  }
}

...so you definitely need to inspect if there is some stray store that
needs to be stamped out with the field initializer. What's worse,
instance/field initializer can invoke an arbitrary method that might
confuse any analysis, but still perform an instance field store. So, we
can reliably do this optimization for simple cases only.

But let's consider a simpler case for concurrency implications:

 class C {
   volatile int v = 0; // can we drop " = 0"?

   public C() {
     // nothing here
   }
 }

Is there a plausible case that shows the semantical difference with
field initializer storing a default value, and without one, that does
*not* also affect single-threaded usages (i.e. it does not follow from
the instance initialization sequence itself)?

Naive attempts to construct the examples seem to boil down to two cases.

*** Case A. Over-reaching "memory effects" for instance fields:

 class A {
   int f;
   volatile int v = 0;

   public A() {
     f = 42;
   }
 }

 A global;

 ----

 Thread 1:
   global = new A();

 Thread 2:
   A a = global;
   if (a != null && a.v == 0) {
     print(a.f); // ?
   }

There are no guarantees here whatsoever, because field initializations
are performed *before* any constructor body starts, as per JLS 12.5.
Therefore, the "releasing" volatile store to $v is executed before any
store to $f, which deconstructs any happens-before.

This seems to expand to non-instance fields, and other variables written
after firing the field initializer.

Note, that perhaps moving the initialization ($v = 0) into the
constructor after ($f = 42), or, similarly, making ($f = 42) the field
initializer, would lay out the code in the correct shape, and this gets
us to case B.


*** Case B. Transitive "memory effects" over explicit stores:

 class B {
   int f = 42;
   volatile int v = 0;
 }

 B global;

 ----

 Thread 1:
   global = new B();

 Thread 2:
   B b = global;
   if (b != null && b.v == 0) {
     print(b.f); // ?
   }

These three executions seem plausible:

 1) Happens-before read of write(f, 42):

  (default) write(v,0)
  (default) write(f,0)
         |
         | hb/sw
         v
     write(f,42) --hb/po--> write(v,0)
                                |
                                | hb/sw
                                v
                             read(v):0 --hb/po--> read(f):42;

 2) Read of default write(f, 0) -- these synchronize-with the first
action in the thread:

                       (default) write(v,0)
                       (default) write(f,0)
                                |
                                | hb/sw
                                v
                             read(v):0 --hb/po--> read(f):0

 (One might wonder if that means you can see the default value for an
explicitly initialized volatile field, once you read the instance itself
-- you can! -- we have seen this with AtomicIntegers before)

 3) Racy read of unordered write(f, 42):

     (default) write(v,0)
     (default) write(f,0)          write(f, 42)
                  |                     .
                  | hb/sw               . (unordered)
                  v                     .
               read(v):0 --hb/po--> read(f):42


In other words, if we drop execution (1) by omitting the explicit
initializing store, we would still be able to observe both "0" and "42".

This seems to expand to non-instance fields, and other variables written
before firing the field initializer.

*** Putting all together

Now, putting these things together:

 * Case A tells that no variable written *after* the volatile instance
field initializer is protected (duh).

 * Case B tells that no variable written *before* the volatile instance
field initializer writing the default value is protected by
happens-before. This is because the explicit initializing store with the
default value is indistinguishable from the default value store.

 * Since Case A and Case B cover all possible variables (with the
exception of the volatile field itself), this seems to imply that
explicit field initializers that store default values have no effect on
memory ordering.

Is it a correct way to think about it?

Thanks,
-Aleksey

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 819 bytes
Desc: OpenPGP digital signature
URL: <http://cs.oswego.edu/pipermail/concurrency-interest/attachments/20151218/06a62a5b/attachment.bin>


More information about the Concurrency-interest mailing list