I'm trying to solve a simple problem and am falling into a Java Memory Model rabbit hole.
What is the simplest and/or most efficient (judgement call here), but race-free (precisely defined according to the JMM) way to write a Java class containing a non-final reference field which is initialized to a non-null value in the constructor and subsequently never changed, such that no subsequent access of that field by any other thread can see a non-null value?
Broken starting example:
public class Holder {
private Object value;
public Holder(Object value) {
if (value == null)
throw NullPointerException();
this.value = value;
}
public Object getValue() { // this could return null!
return this.value;
}
}
And according to this post, marking the field volatile
doesn't even work!
public class Holder {
private volatile Object value;
public Holder(Object value) {
if (value == null)
throw NullPointerException();
this.value = value;
}
public Object getValue() { // this STILL could return null!!
return this.value;
}
}
Is this the best we can do??
public class Holder {
private Object value;
public Holder(Object value) {
if (value == null)
throw NullPointerException();
synchronized (this) {
this.value = value;
}
}
public synchronized Object getValue() {
return this.value;
}
}
OK what about this?
public class Holder {
private Object value;
public Holder(Object value) {
if (value == null)
throw NullPointerException();
this.value = value;
synchronized (this) { }
}
public synchronized Object getValue() {
return this.value;
}
}
Side note: a related question asks how to do this without using any volatile
or synchronization, which is of course impossible.
To safely publish a non-immutable object in Java, You need to synchronise the construction of the object and the write of the shared reference to that object. It's not just the internals of that object that matters in this question.
If you publish an object without proper synchronisation, with reordering, the consumer of a Holder
object can still see a partially constructed object if the reference to the object got published before the constructor finishes. For example Double-checked locking without volatile
.
There are several ways to publish an object safely:
volatile
field or AtomicReference
Note that those bullet points are talking about the reference to the Holder
object, not fields of the class.
So the easiest way would be the first option:
public static Holder holder = new Holder("Some value");
Any thread accessing the static field is going to see a properly constructed Holder
object.
See Section 3.5.3 "Safe publication idioms" of Java Concurrency in Practice. For more information about unsafe publication, see Section 16.2.1 of Java Concurrency in Practice.
The problem you are trying to solve is called safe publication and there exist benchmarks for a best performant solution. Personally, I prefer the holder pattern which also performs the best. Define a Publisher
class with a single generic field:
class Publisher<T> {
private final T value;
private Publisher(T value) { this.value = value; }
public static <S> S publish(S value) { return new Publisher<S>(value).value; }
}
You can now create your instance via:
Holder holder = Publisher.publish(new Holder(value));
Since your Holder
is dereferenced via a final
field, it is guaranteed to be fully initialized by the JMM after reading it from the same final field.
If this is the only usage of your class, then you should of course add a convenience factory to your class and make the constructor itself private
to avoid unsafe construction.
Note that this performs very well as modern VMs erase the object allocation after applying escape anaysis. The minimal performance overhead comes from the remaining memory barriers in the generated machine code which are however required to safely publish the instance.
Note: The holder pattern is not to be confused with your example class being called Holder
. It is the Publisher
that implements the holder pattern in my example.
See section 17.5 of the Java Language Specification.
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.
In other words, as long as we are careful not to leak this
from the constructor of Holder
to another thread, we can guarantee that other threads will see the proper (non-null
) value of ref
without additional synchronization mechanisms.
class Holder {
private final Object ref;
Holder(final Object obj) {
if (obj == null) {
throw new NullPointerException();
}
ref = obj;
}
Object get() {
return ref;
}
}
If you're seeking a non-final field, recognize we can use synchronized
to enforce that get
does not return until ref
is non-null and also ensure a proper happens-before relationship (see: memory barrier) holds on the wrapped reference:
class Holder {
private Object ref;
Holder(final Object obj) {
if (obj == null) {
throw new NullPointerException();
}
synchronized (this) {
ref = obj;
notifyAll();
}
}
synchronized Object get() {
while (ref == null) {
try {
wait();
} catch (final InterruptedException ex) { }
}
return ref;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With