简体   繁体   中英

Safe publication of immutable objects in Java

I want to understand if volatile is needed to publish immutable objects.

For example, assuming we have an immutable object A :

// class A is immutable
class A {
  final int field1;
  final int field2;

  public A(int f1, int f2) {
    field1 = f1;
    field2 = f2;
  }
}

Then we have a class B that is accessed from different threads. It holds a reference to an object of class A :

// class B publishes object of class A through a public filed
class B {
  private /* volatile? */ A toShare;

  // this getter might be called from different threads
  public A getA(){
    return toShare;
  }

  // this might be called from different threads
  public void setA(num1, num2) {
    toShare = new A(num1, num2);
  }
}

From my reading it seems immutable objects can be safely published through any means , so does that mean we don't need to declare toShare as volatile to ensure its memory visibility?

No, you are not guaranteed that you'll be seeing all updates to the toShare field of your shared data. This is because your shared data does not use any synchronization constructs that guarantee its visibility or the visibility of references reachable through it across threads. This makes it open game for numerous optimizations on the compiler and hardware level.

You can safely change your toShare field to reference a String (which is also immutable for all your purposes) and you'll probably (and correctly) feel more uneasy about its update visibility.

Here you can see a rudimentary example I've created that can show how updates are lost without any additional measures to publish changes to the reference of an immutable object. I've ran it using the -server JVM flag on JDK 8u65 and Intel® Core™ i5-2557M, disregarding the possibly thrown NullPointerException and saw the following results:

  • Without safe being volatile , the second thread doesn't terminate because it doesn't see many of the changes made by the first thread

Console output:

[T1] Shared data visible here is 2147483647
  • When safe is changed to be volatile , the second thread terminates alongside the first thread

Console output:

[T1] Shared data visible here is 2147483647
[T2] Last read value here is 2147483646
[T2] Shared data visible here is 2147483647

PS And a question to you - what happens if sharedData (and not safe ) is made volatile ? What could happen according to the JMM?

Answer is NO , it is needed to use volatile or any other way (for example, add synchronized keyword to both signatures get and set) to make a Happens/Before edge. Final fields semantic only guarantees that if someone sees a pointer to an instance of the class, all final fields have their values set according to constructor when it is finished: http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5

And this says nothing about visibility of the reference itself. Since your example uses non-final field

private A toShare;

you have to take care about visibility of the field with volatile or synchronized section or a java.util.concurrent.locks.Locks or AtomicReference etc. to initiate/guarantee cache synchronization. Some useful stuff, BTW, about finals and safe publication http://shipilev.net/blog/2014/safe-public-construction/

http://shipilev.net/blog/2014/all-fields-are-final/

It seems like JMM should take care of the visibility problem for publishing immutable objects, at least that what's said in Concurrency in Practice, 3.5.2 Immutable Objects and Initialization Safely :

Because immutable objects are so important, the JavaMemory Model offers a special guarantee of initialization safety for sharing immutable objects. As we've seen, that an object reference becomes visible to another thread does not necessarily mean that the state of that object is visible to the consuming thread. In order to guarantee a consistent view of the object's state, synchronization is needed.

Immutable objects, on the other hand, can be safely accessed even when synchronization is not used to publish the object reference. For this guarantee of initialization safety to hold, all of the requirements for immutability must be met: unmodifiable state, all fields are final, and proper construction.

Immutable objects can be used safely by any thread without additional synchronization, even when synchronization is not used to publish them.

The following chapter 3.5.3 Safe publication Idioms states that safe publication is required only for non-immutable objects using the following approaches:

  1. Static initializer
  2. Storing reference in volatile/final/AtomicReference
  3. Storing reference that is guarded by the lock

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM