2

java.lang.String is only effectively immutable. Brian Goetz of "Java Concurrency in Practice" said something like effectively immutable objects will only be thread safe if safely published. Now, say I unsafely publish String like this:

public class MultiThreadingClass {
    private String myPath ="c:\\somepath"; 
    //beginmt runs simultaneously on a single instance of MultiThreading class
    public void beginmt(){
        Holder h = new Holder();
        h.setPath(new File(myPath)); //line 6
        h.begin();
    }
}

public class Holder {
    private File path;
    public void setPath(File path){
        this.path = path;
    }
    public void begin(){
        System.out.println(path.getCanonicalPath()+"some string");
    }
}

At the moment that the MultiThreadingClass is initializing with its constructor, it could happen that the File constructor on line 6 may not see the value of myPath.

Then, about three seconds after the construction of the unsafely published String object, threads on MultiThreadingClass are still running. Could there still be a chance that the File constructor may not see the value of myPath?

damat-perdigannat
  • 5,780
  • 1
  • 17
  • 33
  • 1
    Can you trace out your thread's execution more clearly? I don't understand what you're trying to show. – Sotirios Delimanolis Aug 22 '14 at 02:25
  • It's also in the system constant pool. – chrylis -cautiouslyoptimistic- Aug 22 '14 at 02:36
  • @SotiriosDelimanolis I'm trying. – damat-perdigannat Aug 22 '14 at 02:42
  • 3
    @vandale That only applies to `final` fields, not to non-final fields like `myPath`. From the Java Memory Model: "An object is considered to be completely initialized when its constructor finishes. A thread that can only see a reference to an object after that object has been completely initialized is guaranteed to see the correctly initialized values for that object’s **final fields**." – Erwin Bolwidt Aug 22 '14 at 02:43
  • 2
    @b16db0 I think you're confusing the reference and the object. If the reference `myPath`, when executing line 6, does not yet point to `"c:\\somepath"`, then it must have the value `null` (barring other code that you haven't shown). That value is not going to change later retroactively. – Erwin Bolwidt Aug 22 '14 at 02:45
  • @ErwinBolwidt To be clear, what I mean with "the value in myPath" is the char array `value` inside the `String` object. I was thinking this char array could be `null` yet while the String object is not `null` already due to reordering of instructions. – damat-perdigannat Aug 22 '14 at 03:14
  • 2
    @b16db0 Ah OK I see, you don't have to worry about that. The char-array `value` inside `java.lang.String` is a `final` field assigned inside the constructor, and the constructor do not let a reference to `this` escape, so according to the Java Memory Model, there is a guarantee that any thread can only see the correctly initialized value for this `final` field. (If not, there would be many, many [more] security holes in the JVM when it runs applets or other sandboxed code) – Erwin Bolwidt Aug 22 '14 at 03:19
  • 1
    @ErwinBolwidt Thanks, so it's actually the String object itself that might not be visible. – damat-perdigannat Aug 22 '14 at 04:02

2 Answers2

2

Your statement that you are asking your question about:

At the moment that the MultiThreadingClass is initializing with its constructor, it could happen that the File constructor on line 6 may not see the value of myPath.

The answer is complicated. You don't need to worry about the char-array value inside the String object. As I mentioned in the comments, because it is a final field that is assigned in the constructors, and because String doesn't pass a reference to itself before assigning the final field, it is always safely published. You don't need to worry about the hash and hash32 fields either. They're not safely published, but they can only have the value 0 or the valid hash code. If they're still 0, the method String.hashCode will recalculate the value - it only leads to other threads recalculating the hashCode when this was already done earlier in a different thread.

The reference myPath in MultiThreadingClass is not safely published, because it is not final. "At the moment that the MultiThreadingClass is initializing with its constructor", but also later, after the constructor completed, other Threads than the thread that ran the constructor may see the value null in myPath rather than a reference to your string.

There's an example in the Java Memory Model section of the Java Language Specification [version 8 linked but this is unchanged since JMM was released in JSR-133]:

Example 17.5-1. final Fields In The Java Memory Model

The program below illustrates how final fields compare to normal fields.

class FinalFieldExample { 
    final int x; 
    int y; 

    static FinalFieldExample f; 

    public FinalFieldExample() { 
        x = 3; 
        y = 4; 
    } 

    static void writer() { 
        f = new FinalFieldExample(); 
    } 

    static void reader() { 
        if (f != null) { 
            int i = f.x; // guaranteed to see 3 
            int j = f.y; // could see 0 
        } 
    } 
}

The class FinalFieldExample has a final int field x and a non-final int field y. One thread might execute the method writer and another might execute the method reader.

Because the writer method writes f after the object's constructor finishes, the reader method will be guaranteed to see the properly initialized value for f.x: it will read the value 3. However, f.y is not final; the reader method is therefore not guaranteed to see the value 4 for it.

This is even likely to happen on a heavily loaded machine with many threads.

Workarounds/solutions:

  • Make myPath a final field (and don't add constructors that pass out the this reference before assigning the field)
  • Make myPath a volatile field
  • Make sure that all threads accessing myPath synchronize on the same monitor object before accessing myPath. For example, by making beginmt a synchronized method, or by any other means.
Erwin Bolwidt
  • 30,799
  • 15
  • 56
  • 79
  • I noticed that you mentioned in your answer that "also later, after the constructor completed, other Threads than the thread that ran the constructor may see the value null in myPath rather than a reference to your string." Can you explain how this can happen? I can't think of a data-race because no other writes are being done. – damat-perdigannat Aug 22 '14 at 04:04
  • 1
    A thread is not required to ever see any write by a thread unless it has a happens-before relation with the write. That's extreme but a JVM that does this still conforms to the JMM specification. A real situation in which this happens (for a short time) is with multiple CPU's that each have their own cache for writes. A thread running on the second CPU fetches the value from its own cache (which may need to go to main memory), but microseconds before, a thread on the first CPU wrote to `myPath` but the value is still in CPU #1's cache and not written to main memory yet. – Erwin Bolwidt Aug 22 '14 at 04:08
  • Great, I think this is not in Chapter 3 of "Java Concurrency in Practice". – damat-perdigannat Aug 22 '14 at 04:12
  • 1
    The Java Memory Model specification is the best source. It's incorporated in the [Java Language Specification](http://docs.oracle.com/javase/specs/jls/se8/jls8.pdf) nowadays (originally JSR-133). For an example of this issue, see "Example 17.5-1. final Fields In The Java Memory Model" – Erwin Bolwidt Aug 22 '14 at 04:22
  • 1
    @Erwin Bolwidt: it’s not really extreme, it happens all the time within recent JVMs, because the optimizer transforms the reading code into something that never accesses the RAM if there’s no memory barrier within the code. Another reason is that the optimization might have a big impact on the execution timing (that’s what optimization is about) so the assumption derived from source code that the read likely happens after the write might be way off. – Holger Aug 22 '14 at 10:14
  • @Holger Right. I should change that to "it may sound extreme" then. Can't change comments though. Thx. – Erwin Bolwidt Aug 22 '14 at 10:18
1

Could there still be a chance that the File constructor may not see the value of myPath?

Answer is yes it is possible since Java Memory Model guarantees visibility of only final fields :-

"A new guarantee of initialization safety should be provided. If an object is properly constructed (which means that references to it do not escape during construction), then all threads which see a reference to that object will also see the values for its final fields that were set in the constructor, without the need for synchronization."

JSR 133 Link

However I feel this situation is impossible to recreate (I too had tried earlier a similar theory but ended in vain).

There is a case of unsafe publication/escape of this reference within the constructor which can lead to the scenario of myPath not being initialized properly. An example for this is given in Listing 3.7 of the book you mentioned. Below is an example of making your class this reference to escape in constructor.

public class MultiThreadingClass implements Runnable{
    public static volatile MultiThreadingClass unsafeObject;
    private String myPath ="c:\\somepath"; 

    public MultiThreadingClass() {
       unsafeObject = this;
       .....
    }
    public void beginmt(){
        Holder h = new Holder();
        h.setPath(new File(myPath)); //line 6
        h.begin();
    }
}

The above class can cause other threads to access unsafeObject reference even before the myPath is correctly set but again recreating this scenario might be difficult.

Manjunath
  • 1,685
  • 9
  • 9