Search code examples
javaconcurrencythread-safetyatomicvolatile

Are these initialization samples thread-safe?


After reading the comments about @Bozho'answer for When do I need to use AtomicBoolean in Java?, I'm a little confused about how to implement thread-safe initialization by using Atomic class or volatile boolean.

So I write these samples, but not sure which is thread-safe or not?

class InitializationSample1 {
    private AtomicBoolean initialized = new AtomicBoolean(false);

    public void init(){
        if (initialized.compareAndSet(false, true)) {
            initialize();
        }
    }

    private void initialize(){}

    public boolean isInitialized(){
        return initialized.get();
    }
}

class InitializationSample2 {
    private volatile boolean initialized;

    public void init(){
        if (initialized) return;
        synchronized (this){
            if (initialized) return;

            initialize();

            initialized = true;
        }
    }

    private void initialize(){}

    public boolean isInitialized(){
        return initialized;
    }
}

class InitializationSample3 {
    private AtomicBoolean initStarted = new AtomicBoolean(false);
    private AtomicBoolean initCompleted = new AtomicBoolean(false);

    public void init(){
        if (initStarted.compareAndSet(false, true)){
            initialize();
            initCompleted.set(true);
        }
    }

    private void initialize(){}

    public boolean isInitialized(){
        return initCompleted.get();
    }
}

class InitializationSample4 {
    private AtomicInteger initialized = new AtomicInteger(0);

    public void init(){
        if (initialized.compareAndSet(0, 1)){
            initialize();
            initialized.set(2);
        }
    }

    private void initialize(){}

    public boolean isInitialized(){
        return initialized.get() == 2;
    }
}

class InitializationSample5 {
    private volatile boolean initialized;

    private AtomicBoolean once = new AtomicBoolean(false);

    public void init(){
        if (once.compareAndSet(false, true)){
            initialize();
            initialized = true;
        }
    }

    private void initialize(){}

    public boolean isInitialized(){
        return initialized;
    }
}

I know Sample1 is not thread-safe for that calling isInitialized() may return true when initialization not completed.

And Sample2 should be thread-safe, it's from the classic double-check locking Singleton implemention.

How about Sample3~5?

UPDATE. I may need to make my question more specific. Assuming that the initialize() method may create some objects(and there may be alse some getXX() method to get these objects.), and make some fields assignment. So, when I get true from calling the isInitialized() method, may I get those objects properly constructed from any thread? And are those fields assignment operation visible?

UPDATE. @pveentjer , I have updated one of the samples and added the usage of it.

I know it's definitely not appropriate to be used on actual programming scene. It's just for discussing here.

class InitializationSample3 {
    private AtomicBoolean initStarted = new AtomicBoolean(false);
    private AtomicBoolean initCompleted = new AtomicBoolean(false);

    private Foo foo;
    private int someField;

    public void init(){
        if (initStarted.compareAndSet(false, true)){
            initialize();
            initCompleted.set(true);
        }
    }

    private void initialize(){
        foo = new Foo();
        someField = 123;
    }

    public boolean isInitialized(){
        return initCompleted.get();
    }

    public Foo getFoo(){
        return foo;
    }

    public int getSomeField(){
        return someField;
    }

    public static void main(String[] args) {
        InitializationSample3 sample = new InitializationSample3();

        // the initialization may be done when the application just starts.
        new Thread(() -> {
            sample.init();
        }).start();

        // at some point after the application started, check if it is initialized
        // and get the fields from the initialized object.
        new Thread(() -> {
            while (!sample.isInitialized()){
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            // Can I get the foo object fully constructed, and the new value 
            // of someField?
            System.out.println(sample.getFoo());
            System.out.println(sample.getSomeField());
        }).start();
    }
}

Solution

  • Based on the code, it seems they are all thread-safe in the sense that they will protect against repeated instantiation.

    Is it that important to be able to ask an object if it is initialized? Normally you don't want to expose that kind of functionality.

    The problem with the ones that don't use a lock is that you can return from the initialize that is in progress and still need to deal with an object that hasn't fully completed its initalization.

    So what do you want to accomplish?

    There are better approach to deal with the object initialization that doesn't need to deal with volatile and locks at all.

    Check 3.2 Initialization On Demand