JavaMemoryModel: subtle details for final fields

From: Bill Pugh (pugh@cs.umd.edu)
Date: Wed Aug 29 2001 - 13:21:18 EDT


OK, I've been trying to work out the details of final fields.

We've often just said "Well written code should not store into the
heap a reference to an object being constructed until the
construction (including all subclass constructors) of that object is
finished."

OK, that is a nice ideal situation, but it isn't realistic. There are
lots of situations that require violating that simple rule, and lots
of existing code that violates it. So we need something for final
fields that will work even when that rule is violated.

If you follow the no-store rule, there you are guaranteed simple
semantics for final fields, without any need to consider the more
complicated rules.

To handle the more difficult cases, here are some guidelines for
writing code that I think we can accommodate (these rules don't
handle constructor chaining; they aren't hard to add, but they add
complications we don't want to deal with initially).

G1: There are "good" references to objects and "bad" references to objects.
   Reads of final fields through "good" references are guaranteed to get the
   desired semantics, reads of "bad" references are not.

G2: "Bad" references can be turned into "good" references, but a "good"
   reference stays good. For example, if a thread stores a "good" reference
   into the heap and another thread reads that value, it sees a "good"
reference.

G3: Allocation returns a "bad" reference. Within the thread performing
   construction, all local references to an object being constructed become
   "good" at the termination of the constructor.

G4: If a thread reads a heap location containing a "bad" reference, but the
   read is constrained by a happens-before relationship (via synchronization)
   to occur after the object is froze, the threads reads a "good" reference
   into thread local storage.

G5: (A generalization of G4) If a thread follows a chain of references (e.g.,
   A.global.next.next.next.next) to reach an object X, then if any of references
   were written into the heap at a point constrained by a happens-before
   relationship (via synchronization) to occur after the object is frozen, the
   threads reads a "good" reference into thread local storage.

I wrote up 4 litmus tests:

t1/A.java - shows a case where a reference to an object is stored into a
   static variable before the object is fully constructed, but synchronization
   is used to prevent any other thread from reading that static variable until
   after synchronization is complete.

t2/A.java - similar to t1/A.java, but the synchronization doesn't guard reading
   the static variable, only uses of the variable read. In this case,
the special
   semantics of final fields are not enforced. This is motivated by the fact
   that we want to make it legal for a compiler to load final fields of an
   object immediately after loading a reference to that object, and not have
   to reload them at synchronization points.

   The second read of the static variable in t2/A.java is protected by
   synchronization, and so will get the proper semantics for final
fields. (I.e.,
   it will get a "good" reference to the object.

t3/A.java - While an A object is being constructed, an inner B object
   is constructed; this stores a reference to the A object into the this$0
   field of the B object. Since T2 must go through a field (A.global) written
   after the A object is fully constructed in order to get to the A object, it
   will load a "good" reference to the A object and get the proper semantics
   for final fields. This example motivated G5

t4/A.java - A generalized application of G5, showing some of the strange
   things you can do with it.

Thoughts?

        Bill

::::::::::::::
t1/A.java
::::::::::::::
class A {
   final int x;
   static A global;

   A() {
    global = this; // publish object
    x = 42;
    };

   static synchronized void T1() {
        new A();
     }

   static void T2() {
     A a;
     synchronized (A.class) {
        a = global;
        }
     if (a == null) return;
     int r1 = a.x;
     assert r1 == 42; // see final value
     }
}
::::::::::::::
t2/A.java
::::::::::::::
class A {
   final int x;
   static A global;

   A() {
    global = this; // publish object
    x = 42;
    };

   static synchronized void T1() {
        new A();
     }

   static void T2() {
     A a = global;
     synchronized (A.class) {
       if (a == null) return;
       int r1 = a.x;
       // possible r1 != 42; // might not see final value
       a = global; // reload global at a point definitely after construction
       int r2 = a.x;
       assert r1 == 42;
       }
     }
}
::::::::::::::
t3/A.java
::::::::::::::
class A {
   class B {
        int getX() { return x; }
        }
   B b;
   final int x;

   A() {
    b = new B(); // causes reference to constructed A object to be stored
                // into heap before the A is fully constructed
    x = 42;
    };

   static B global;
   static void T1() {
        A a = new A();
        global = a.b;
     }

   static void T2() {
        B b = global;
        if (b == null) return;
        int r1 = b.getX();
        assert r1 == 42; // must see final value
     }
}
::::::::::::::
t4/A.java
::::::::::::::
class A {
   static class Node {
        Object next;
        Node(Object n) { next = n; }
        Node() {}
        }
   static Node n3 = new Node();
   static Node n2 = new Node();
   static Node n1 = new Node(n2);
   final int x;

   A() {
      x = 42;
      n3.next = this;
    };

   static void T1() {
        new A();
        n2.next = n3;
     }

   static void T2() {
        Object o = n1.next; // will be n2
        o = ((Node)o).next; // read n2.next
        if (o == null) return;
        // since non-null, must be n3
        o = ((Node)o).next; // read n3.next
        if (o == null) return;
        int r1 = ((A)o).x;
        assert r1 == 42; // must see final value
     }
}
-------------------------------
JavaMemoryModel mailing list - http://www.cs.umd.edu/~pugh/java/memoryModel



This archive was generated by hypermail 2b29 : Thu Oct 13 2005 - 07:00:34 EDT