I want to implement a wrapper class. The class's only public facing stuff are:
Supplier<WrappedType>
, perhaps.With the following rule on its behaviour: the logic that creates the wrapped class can have side effects and must only be called once. And, obviously, the getter method should always actually return the same wrapped class instance, which should actually be the result of running the logic that was passed in to the constructor.
I think I have this code that does what I want, but I'm not sure how to test if it is guaranteed to work or not or if there is a better way to do it.
package foo;
import java.util.function.Supplier;
public final class ConcurrentLazyContainer<A> {
private Supplier<A> supplier;
private A value;
public ConcurrentLazyContainer(Supplier<A> supplier) {
this.supplier = supplier;
value = null;
}
public synchronized A get() {
if (value == null) {
value = supplier.get();
supplier = null;
}
return value;
}
}
Does just using synchronized
get me all the way to what I want here? Do my fields need to be volatile too, perhaps?
I wrote a test that spins up new threads calling the same wrapper but it doesn't appear to me that supplier gets called more than once, which is weird because I don't really understand why volatile wouldn't be necessary here.
The comments on the question are right: if you only access value
within the synchronized
method, then you don't need it to be volatile
as well. However, in some cases, you may be able to improve performance with double-checked locking.
public final class Lazy<T> {
private final Supplier<? extends T> initializer;
private volatile T value;
public Lazy(Supplier<? extends T> initializer) {
this.initializer = initializer;
}
public T get() {
T result = this.value;
if (result == null) {
synchronized (this) {
result = this.value;
if (result == null) {
this.value = result = this.initializer.get();
}
}
}
return result;
}
}
This code is based on some examples shown in Effective Java and Java Concurrency in Practice. Note that this code checks twice to see if result
is null
, once outside the synchronized
block, and once inside. The advantage to this is that you won't need to synchronize if the value already exists. Note that with this strategy, value
must be volatile
because it is being accessed outside of the synchronized
block.