14

I want to make sure that I correctly understand the 'Effectively Immutable Objects' behavior according to Java Memory Model.

Let's say we have a mutable class which we want to publish as an effectively immutable:

class Outworld {
  // This MAY be accessed by multiple threads
  public static volatile MutableLong published;
}

// This class is mutable
class MutableLong {
  private long value;

  public MutableLong(long value) {
    this.value = value;
  }

  public void increment() {
    value++;
  }

  public long get() {
    return value;
  }
}

We do the following:

// Create a mutable object and modify it
MutableLong val = new MutableLong(1);
val.increment();
val.increment();
// No more modifications
// UPDATED: Let's say for this example we are completely sure
//          that no one will ever call increment() since now

// Publish it safely and consider Effectively Immutable
Outworld.published = val;

The question is: Does Java Memory Model guarantee that all threads MUST have Outworld.published.get() == 3 ?

According to Java Concurrency In Practice this should be true, but please correct me if I'm wrong.

3.5.3. Safe Publication Idioms

To publish an object safely, both the reference to the object and the object's state must be made visible to other threads at the same time. A properly constructed object can be safely published by:
- Initializing an object reference from a static initializer;
- Storing a reference to it into a volatile field or AtomicReference;
- Storing a reference to it into a final field of a properly constructed object; or
- Storing a reference to it into a field that is properly guarded by a lock.

3.5.4. Effectively Immutable Objects

Safely published effectively immutable objects can be used safely by any thread without additional synchronization.

reevesy
  • 3,452
  • 1
  • 26
  • 23
Volodymyr Sorokin
  • 1,735
  • 3
  • 14
  • 20
  • Please show the [*static initializer*](http://docs.oracle.com/javase/tutorial/java/javaOO/initial.html) that establishes the object's state before a reference to it is made visible. – trashgod Apr 20 '12 at 23:07

3 Answers3

9

Yes. The write operations on the MutableLong are followed by a happens-before relationship (on the volatile) before the read.

(It is possible that a thread reads Outworld.published and passes it on to another thread unsafely. In theory, that could see earlier state. In practice, I don't see it happening.)

Tom Hawtin - tackline
  • 145,806
  • 30
  • 211
  • 305
  • 2
    Your comment (2nd paragraph) seems incompatible with your answer (1st paragraph) and the happens before linked to the volatile read. Can you elaborate? – assylias Apr 21 '12 at 09:07
  • 5
    Suppose thread T1 safe publishes to T2. There is a *happens-before* from T1 to T2. But if the same object is unsafely published from T2 to T3, then there is no *happens-before* from T2 to T3 and therefore also no *happens-before* from T1 to T3. – Tom Hawtin - tackline Apr 21 '12 at 12:32
  • 1
    It’s also possible that a thread reads `Outworld.published` and gets `null`. – Holger Feb 22 '22 at 13:38
  • Tom Hawtin - tackline, how can T2 unsafely publish to T3 under assumtion that the MutableLong won't ever be modified after construction? – RoK Apr 16 '23 at 07:58
5

There is a couple of conditions which must be met for the Java Memory Model to guarantee that Outworld.published.get() == 3:

  • the snippet of code you posted which creates and increments the MutableLong, then sets the Outworld.published field, must happen with visibility between the steps. One way to achieve this trivially is to have all that code running in a single thread - guaranteeing "as-if-serial semantics". I assume that's what you intended, but thought it worth pointing out.
  • reads of Outworld.published must have happens-after semantics from the assignment. An example of this could be having the same thread execute Outworld.published = val; then launch other the threads which could read the value. This would guarantee "as if serial" semantics, preventing re-ordering of the reads before the assignment.

If you are able to provide those guarantees, then the JMM will guarantee all threads see Outworld.published.get() == 3.


However, if you're interested in general program design advice in this area, read on.

For the guarantee that no other threads ever see a different value for Outworld.published.get(), you (the developer) have to guarantee that your program does not modify the value in any way. Either by subsequently executing Outworld.published = differentVal; or Outworld.published.increment();. While that is possible to guarantee, it can be so much easier if you design your code to avoid both the mutable object, and using a static non-final field as a global point of access for multiple threads:

  • instead of publishing MutableLong, copy the relevant values into a new instance of a different class, whose state cannot be modified. E.g.: introduce the class ImmutableLong, which assigns value to a final field on construction, and doesn't have an increment() method.
  • instead of multiple threads accessing a static non-final field, pass the object as a parameter to your Callable/Runnable implementations. This will prevent the possibility of one rogue thread from reassigning the value and interfering with the others, and is easier to reason about than static field reassignment. (Admittedly, if you're dealing with legacy code, this is easier said than done).
Grundlefleck
  • 124,925
  • 25
  • 94
  • 111
  • I completely agree with what you say. Synchronization is too easy to get wrong. I would avoid it at any cost. Shared mutability is just not a safe way of designing an algorithm or program. I think every programmer should dedicate some time into studying of functional programming concepts and apply them to the oo world. It's definitely worth it. – bennidi Apr 22 '12 at 09:33
3

The question is: Does Java Memory Model guarantee that all threads MUST have Outworld.published.get() == 3 ?

The short answer is no. Because other threads might access Outworld.published before it has been read.

After the moment when Outworld.published = val; had been performed, under condition that no other modifications done with the val - yes - it always be 3.

But if any thread performs val.increment then its value might be different for other threads.

Eugene Retunsky
  • 13,009
  • 4
  • 52
  • 55
  • Yes, I'm talking about situation when we may consider this object as Effectively Immutable; i.e we are completely sure that no other thread will ever call `increment()` on it. I updated an example to be more specific. – Volodymyr Sorokin Apr 22 '12 at 09:20