can the lazy init be done safely this way? We had a dispute where a colleague was claming for some first threads it could fail and they would see the null value.
public class TestAtomicReference {
static final AtomicReference<String> A = new AtomicReference<>();
public static void main(String[] args) throws Exception {
Callable<Integer> callable = () -> {
lazyInit();
return 0;
};
ExecutorService executorService = Executors.newFixedThreadPool(50);
for (int i = 0; i < 1000; i++) {
A.set(null);
List<Future<Integer>> futures = executorService.invokeAll(Collections.nCopies(50, callable));
futures.forEach(f -> {
try {
f.get();
} catch (Exception ignore) {
}
});
}
executorService.shutdown();
executorService.awaitTermination(10, TimeUnit.SECONDS);
}
private static void lazyInit() {
** if (A.get() == null) {
A.compareAndSet(null, costlyGetValue());
}
**
if (A.get() == null) {
System.out.println("INIT FAILURE!");
}
}
private static String costlyGetValue() {
return "A";
}
}
The separation of get & compareAndSet is there to avoid a costly get every time. This test code never fails... Thanks
It does look like your code will work as only one thread can change the atomic reference from null to not null - see compareAndSet(V expectedValue, V newValue). From that point, all threads will see non-null
A.get()However it isn't a good way to lazy initialise as you could have many threads evaluate
a.get() == null. Therefore all but one of your initial calls may evaluatecostlyGetValue()and all but one result of these costly call are discarded. This could cause serious issues on systems, say, when the operation uses significant I/O or database access.You can demonstrate by changing the test code like this and which may show that up to 50 threads call
costlyGetValue():Instead you can change the lazy initialisation to be handled by an holder class. The JVM will initialise the holder class once only, making just one call to
costlyGetValue(). You can useHolder.getInstance()whenever you wish to read the value: