JavaMemoryModel: Mutually exclusive goals for Java memory model

From: Bill Pugh (pugh@cs.umd.edu)
Date: Thu Apr 04 2002 - 12:07:55 EST


OK, we have figured out that two of the goals for the Java memory
model are mutually exclusive in any reasonable memory model

* Goal 1: Make the existing Sun JDK code for StringBuffers and String thread
        safe by changing the fields of String to final. In the existing code,
        all StringBuffer methods are synchronized, and there is a boolean
        that says whether the backing char[] is read only. When a StringBuffer
        is turned into a String, the String reuses the existing char [],
        but the StringBuffer now knows that the char[] is read only, and
        does a copy on write if needed.

* Goal 2: Allow the compiler to do standard compiler optimization techniques.

        Specifically, the compiler should be able to transform:

        Point p1 = Foo.p;
        int x1 = p1.x;
        Point p2 = Foo.p;
        if (p1 == p2) {
                int x2 = p2.x;
                ...
                }

        into

        Point p1 = Foo.p;
        int x1 = p1.x;
        Point p2 = Foo.p;
        if (p1 == p2) {
                int x2 = x1;
                ...
                }

        More generally, assume that within a thread there are
        two reads, r1 and r2, of the same variable v (i.e., reads of the same
        heap memory location; a field or an array element). Unless
there is some
        kind of synchronization between r1 and r2 that would force v to be
        reloaded, Cliff wants full freedom to reuse the value of r1 for the
        value of r2. Since synchronization never forces a final field to be
        reloaded, he should always be able to do this reuse of v is final.

OK, these goals are mutually exclusive in any reasonable memory model.
Consider an example that shows the effect of the behavior allowed by goal 2:

Initially,

    Foo.sb = new StringBuffer("a");
    Foo.s = null

Thread 1
    StringBuffer sb1 = Foo.sb;
    char c1a = sb1.charAt(0); // sees 'a'
    String s1b = Foo.s;
    char c1b = s1b.charAt(0); // might see 'a' or 'b', or throw null pointer
    char c1c = s1b.charAt(0); // might see 'a' or 'b'

Thread 2
    StringBuffer sb2 = Foo.sb;
    sb2.setCharAt(0,'b');
    Foo.s = new String(sb2);

The problem is that the char[] backing the StringBuffer is visible to
Thread 1 before it is turned into a String by thread 2 and the 3
charAt calls in thread 1 all access the same variable: the first
element of the character array used in both the StringBuffer and the
String. Since there is no synchronization, the value of c1a can be
reused for c1b and c1c.

For the transformation Cliff wants to do, the user program compared
p1 and p2, allowing the compiler to reason that p1.x and p2.x
referred to the same memory location. However, I am not willing to
let comparing two registers have semantics in the memory model; it is
just too ugly.

OK, if we abandon goal 1, then in the general case we will force
copying when we convert from a StringBuffer into a String. On the
other hand, we will be able to get rid of the synchronization in
StringBuffer. In special cases where we can determine that the
StringBuffer hasn't leaked, we might be able to avoid the copying.

Cliff has been talking about the advantages of an VM implementation
in which the char's of a String are always inlined. However, this
implementation would also force copying for substring operations.

So we need to decide what to do. We bent over backwards trying to
handle the existing StringBuffer code. We now see that we may have
bent over too far. I can make the memory model go either way, we just
need to decide what we want to do here.

        Bill
-------------------------------
JavaMemoryModel mailing list - http://www.cs.umd.edu/~pugh/java/memoryModel



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