It appears the Java Memory Model does not define "refreshing" and "flushing" of the local cache, instead people only call it that way for simplicity, but actually the "happens-before" relationship implies refreshing and flushing somehow (would be great if you can explain that, but not directly part of the question).
This is getting me really confused combined with the fact that the section about the Java Memory Model in the JLS is not written in a way which makes it easy to understand.
Therefore could you please tell me if the assumptions I made in the following code are correct and if it is therefore guaranteed to run correctly?
It is partially based on the code provided in the Wikipedia article on Double-checked locking, however there the author used a wrapper class (FinalWrapper
), but the reason for this is not entirely obvious to me. Maybe to support null
values?
public class Memoized<T> {
private T value;
private volatile boolean _volatile;
private final Supplier<T> supplier;
public Memoized(Supplier<T> supplier) {
this.supplier = supplier;
}
public T get() {
/* Apparently have to use local variable here, otherwise return might use older value
* see https://jeremymanson.blogspot.com/2008/12/benign-data-races-in-java.html
*/
T tempValue = value;
if (tempValue == null) {
// Refresh
if (_volatile);
tempValue = value;
if (tempValue == null) {
// Entering refreshes, or have to use `if (_volatile)` again?
synchronized (this) {
tempValue = value;
if (tempValue == null) {
value = tempValue = supplier.get();
}
/*
* Exit should flush changes
* "Flushing" does not actually exists, maybe have to use
* `_volatile = true` instead to establish happens-before?
*/
}
}
}
return tempValue;
}
}
Also I have read that the constructor call can be inlined and reordered resulting in a reference to an uninitialized object (see this comment on a blog). Is it then safe to directly assign the result of the supplier or does this have to be done in two steps?
value = tempValue = supplier.get();
Two steps:
tempValue = supplier.get();
// Reorder barrier, maybe not needed?
if (_volatile);
value = tempValue;
Edit: The title of this question is a little bit misleading, the goal was to have reduced usage of a volatile field. If the initialized value is already in the cache of a thread, then value
is directly accessed without the need to look in the main memory again.