I'm seeking an answer to a question similar to Is it appropriate to use AtomicReference.compareAndSet to set a reference to the results of a database call? but with different requirement.
The goal is to create an instance of ObjectWithSideEffectConstructor
only once to avoid duplicate side effects. The construction must happen in setUp()
. Multiple threads will call setUp()
. Similarly there will be a tearDown()
for reclaiming the resource from the object, which is omitted here. Question: what is the best practice to achieve the goal?
Simply using AtomicReference
will not be enough, because the constructor will be executed first, so as the side effect.
private static AtomicReference<ObjectWithSideEffectConstructor> ref =
new AtomicReference<ObjectWithSideEffectConstructor>()
void setUp() {
ref.compareAndSet(null, new ObjectWithSideEffectConstructor());
}
Using the answer from Is it appropriate to use AtomicReference.compareAndSet to set a reference to the results of a database call? will not work, because volatile
lacks of synchronization. There will be window that multiple threads enters if
.
private static volatile ObjectWithSideEffectConstructor obj;
void setUp() {
if (obj == null) obj = new ObjectWithSideEffectConstructor();
}
Simple fix would be
private static ObjectWithSideEffectConstructor obj;
private static final Object monitor = new Object();
void setUp() {
synchronized (monitor) {
if (obj == null) obj = new ObjectWithSideEffectConstructor();
}
}
Similarly, DCL with volatile monitor may give better read performance. But both requires some level of synchronization, thus expect worse performance.
Also we can use FutureTask
. It is more efficient because once the object is created, subsequent FutureTask.get()
will return without blocking. But it is definitely much more complicated than synchronized
.
private static final AtomicReference<FutureTask<ObjectWithSideEffectConstructor>> ref =
new AtomicReference<FutureTask<ObjectWithSideEffectConstructor>>();
void setUp() {
final FutureTask<ObjectWithSideEffectConstructor> future =
new FutureTask<ObjectWithSideEffectConstructor>(
new Callable<ObjectWithSideEffectConstructor>() {
@Override
public ObjectWithSideEffectConstructor call() throws InterruptedException {
return new ObjectWithSideEffectConstructor();
}
}
);
if (ref.compareAndSet(null, future)) future.run();
ref.get().get();
}
Thanks for suggestions.