1

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

1 Answer 1

0

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 evaluate costlyGetValue() 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():

private static String costlyGetValue() {
    System.out.println(Thread.currentThread()+" costlyGetValue()");
    // if it doesn't show, add sleep here too
    return "A";
}

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 use Holder.getInstance() whenever you wish to read the value:

static class Holder {
    private static final Holder INSTANCE = new Holder();
    private final String costlyValue;
    private Holder() {
        this.costlyValue = costlyGetValue();
    }
    public static String getInstance() {
        // Useful to add here: 
        // Objects.requireNonNull(INSTANCE.costlyValue);
        return INSTANCE.costlyValue;
    }
}
private static void lazyInit() {
    if (Holder.getInstance() == null) {
        System.out.println("INIT FAILURE!");
    }
}
Sign up to request clarification or add additional context in comments.

1 Comment

Thx, yes it is understood that more threads could go through the first null check and evaluate the costly get. But only upon the initialization not in the rest of the normal calls (code not shown). It is basically similar to the double check lock pattern but without blocking synchronization. That's not really the issue I was after. The main point was will any thread ever "slip" and end up in the INIT FAILURE ? I think not, right ?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.