OK. I'm trying to nail down the corner cases of final fields.
I think we've got agreement on what happens if, during the construction
of an object, you don't store a reference to the object into the heap.
The problem is what happens when you do. For example, what if during
the construction of an object, you create an inner class of that object.
That stores a reference to the object being created into the heap. Yet,
this certainly seems like a reasonable thing to do.
Previously, I'd said things like: if an object is made visible to another
threads during construction, then the final fields of the object are only
pseudo-final.
I now see two problems with that:
* It doesn't formally define what "made visible to another thread" means
* If one thread sees the object early, it means that the special final
semantics are broken for all threads, probably not a desirable
semantics.
So I'd like to change the definition slightly.
For proper semantics, you must ensure that no other thread
can load a reference to an object with final fields until
the corresponding constructor has completed.
If another thread does load a reference to an object with
final fields before the corresponding constructor has
terminated, then loads of the final fields of that object
may produce default values. However, this only effects
final fields loaded through the reference that was loaded
prematurely. Other references to the same object will see
the correct semantics for final fields (assuming that
those references are not loaded until the constructor completes).
The nice advantage of this is that a read only effects the value
read. If you place a logging/debugging statement somewhere,
perhaps improperly synchronized, it doesn't effect the rest of your
program. This was highlighted by Gao and Vivek as a desirable
feature of a memory model.
OK. On to an example that makes it real. The class below
shows an example of a class with a constructor
that makes the object visible before the object is fully
constructed (note that the static methods are synchronized;
it is key to understanding this example). The first
read of F1.f by foo is premature. Reads of the final fields
via that reference may return default values. However, the
second read of F1.f by foo is correctly synchronized, so
it is guaranteed to see the proper values for final fields.
Comments?
Bill
----class F1 { final int x; static F1 f; F1(int i) throws InterruptedException { f = this; F1.class.wait(); x = i; F1.class.notify(); } static synchronized void foo() throws InterruptedException { F1 tmp1 = F1.f; int i = tmp1.x; F1.class.notify(); F1.class.wait(); int j = tmp1.x; int k = F1.f.x; System.out.println("i = " + i + ", j = " + j + ", k = " + k);
// Under existing semantics, must print i = 0, j = 42, k = 42
// Under proposed semantics, may print // either i = 0, j = 42, k = 42 // or i = 0, j = 0, k = 42
// May not print i = 0, j = 0, k = 0 under either }
public static synchronized void main(String args[]) throws Exception {
new Thread() { public void run() { try { F1.foo(); } catch (InterruptedException e) {} } }.start(); new F1(42); } }
------------------------------- JavaMemoryModel mailing list - http://www.cs.umd.edu/~pugh/java/memoryModel
This archive was generated by hypermail 2b29 : Thu Oct 13 2005 - 07:00:26 EDT