JavaMemoryModel: The final word (fat chance) on final

From: Doug Lea (dl@altair.cs.oswego.edu)
Date: Thu Jul 29 1999 - 11:34:36 EDT


Before the issue slips away, here's an attempt to summarize my notes on final.

The main point of my proposal can be stated as a declarative
constraint, without explicit reference to memory model:

  All accesses of a final field via the reference returned by a constructor
  (or any copy made of that reference to another variable, argument,
  return value, or implicitly as `this' in a self-call) will obtain
  the value of the field written during construction of the object.
    (Further, by existing rules, only one such write occurs.)

But to accommodate the fact that array elements cannot be declared as final,
the rule must be extended with a messier and weaker clause:
   
  If
    * a new array is constructed, and
    * a value is assigned to the ith element of that array (for any i), and
    * the array is bound to a final field in a constructor
  Then
    all accesses of the ith element obtained via the reference
    returned from the constructor (or any copy...) will obtain
    a value that has been assigned to that element.
      (Not necessarily the most recently assigned value.)

If you are busy, you can stop reading now. Otherwise, here are some
notes and comments:

  * This rule is already supported/enforced by the language/compiler for
    the current thread (i.e., the one that runs the constructor).
    The proposed form just explicitly extends the semantics of final
    to apply to other threads.

    The main motivations are, again:
      * Everyone would be shocked and dismayed if this were NOT guaranteed.
      * It solves all initialization safety problems
          that I believe need solving (see below).

    But, as everyone knows, the down side is:
      * Support on some JVMs entails run-time assistance.

  * The explicit mention of `constructor' here avoids having to deal
    with (re)orderings within constructors etc, so evades explicit
    interactions with other memory model issues, at the expense of
    having to deal with special methods (i.e., <init>) when
    integrating with rest of memory model.

  * The array clause merely disallows seeing default zeroes in cases
    where elements of final array fields have somehow been written
    to. Programmers are on their own to ensure that they only write
    once, it that is their intent.

  * The rule can be stated negatively to highlight the problems
    that can arise:

       Access of a final field via a reference obtained from *within*
       a constructor (or any copy ...) need not obtain the value
       written in the constructor.

  * The current handling of final by compilers is actually closer to
    the above rule than the broader interpretation than most people
    (sensibly) have in their heads. For example, the following class
    hits a compile-time error on javac 1.2.x:
    
          class C1 {
            final int aFinalField;

            C1(int a) {
              System.out.println(aFinalField);
              aFinalField = a;
            }
          }

         C1.java:6: Variable aFinalField may not have been initialized.
               System.out.println(aFinalField);

    But the escape of this via a self-call to another method disables
    the usual detection of read-before-write. This version compiles
    without error:

          class C2 {
            final int aFinalField;

            C2(int a) {
              f();
              aFinalField = a;
            }

            void f() { System.out.println(aFinalField); }
          }

  * A stronger version of this rule would not allow `this' to escape,
    but it does not seem feasible or desirable to add this to
    language. However, compilers that issue warnings about detected
    escapes would be useful.

This compromise between complete vs complete lack of initialization
safety enforces only those aspects of initialization safety that
programmers must sometimes explicitly rely on, and does so only if
programmers declare that they do rely on them:

  * The main effect for most programmers is that classes with
    semantics that critically depend on immutability must add
    associated final declarations, and must exercise care in
    constructors. (They should be doing this already, but since
    blank finals weren't introduced until 1.1, and were broken
    in some 1.1.X compilers, they probably don't.)

  * The main effect for Java security efforts is that some JDK
    classes will need to add some final declarations and possibly
    rework constructors in order to avoid potential hostile safety
    violations.

  * The main effect for JVM implementors is that barriers must be placed
    around writes and reads whenever their need cannot be ruled out.
    This again places additional pressure on development of algorithms
    that can quickly rule out as many as possible.

  * The main effect for hacker-level programmers is that
    they will need to learn some new hacks. As Josh and I discovered,
    the rule enables some weird/clever/sleazy double-check and
    single-unsynched-check lazy initialization tricks, for example via
    indirection using:

      class FinalRef {
        public final Object ref; // guaranteed traversable
        FinalRef(Object r) { ref = r; }
      }

    This parallels some similar tricks I've used with similar class
    VolatileRef. These are clearly hacker-level idioms and are hardly
    ever useful, but they are a bit less underhanded than current ways
    to do them because they explicitly declare what they are up to.

  * The main effect for many application-level programmers is that
    since there are no guarantees about write-once fields that are not
    or cannot be established within constructors, people need to use
    synch or volatile to deal with them. (Again, they should already,
    but probably don't.) People who do not use synch in the kinds of
    init() methods common in javabeans, awt, applets, etc (as well as
    in methods that rely on values established in such init() methods)
    may be in for surprises. But they hardly ever will be. Between
    how system event thread and applet threads work, and requirements
    to conform to Swing single-threaded rule, surprises should occur
    only when novices add threads, at which point they must
    contemplate other synch issues anyway that will hopefully drive
    them to do the right thing here.

-- 
Doug Lea, Computer Science Department, SUNY Oswego, Oswego, NY 13126 USA
dl@cs.oswego.edu 315-341-2688 FAX:315-341-5424 http://gee.cs.oswego.edu/  
-------------------------------
JavaMemoryModel mailing list - http://www.cs.umd.edu/~pugh/java/memoryModel



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