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