0

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();
    }
}
R.h
  • 60
  • 8
  • The problem is that your InitializationSample3 init isn't blocking.So if the second thread wants to call init and the object is already in the process of being initialized, it will immediately return and call 'getFoo' on an object that isn't initialized. That is why I suggested to use InitializationSample2. Add a 5s sleep in the initialize before you set foo/someField and print the returned object 'getFoo/getSomeField' and check what you get. – pveentjer Jul 16 '21 at 04:57
  • Yes, I realized the problem. How about checking isInitialized( ) by blocking, it will be alright ? – R.h Jul 16 '21 at 06:21
  • Why expose the isInitialized at all :) It is easier to always call init and let the init method deal with it. Also a method named 'isInitialized' should not block for the duration of the initialization; that would be misleading. – pveentjer Jul 16 '21 at 06:24
  • I would really urge you to use InitializationSample2. This has the blocking behavior your are looking for so you don't need to do polling. – pveentjer Jul 16 '21 at 06:27
  • Thank you so much, really appreciate your patience. – R.h Jul 16 '21 at 06:45

1 Answers1

0

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

pveentjer
  • 10,545
  • 3
  • 23
  • 40
  • Thanks! These samples is just for showing what I know about Atomic classes and volatile boolean. I have lack of experience with concurrent programming, I'm not so sure if I can use these concurrency tools like this. I know the static internal instance-holder class for initialization is the right way. But like I said, I just wanna figure out the usage of these atomic classes and volatile key word. I have re-edited my question to make it more specific. – R.h Jul 16 '21 at 03:43
  • Can you update your examples so that you actually instantiate some bogus fields. – pveentjer Jul 16 '21 at 03:53
  • Also because there are so many examples, it is becoming pretty messy. So I would suggest trimming down the number of examples to just a single one. Also you need to add the actual usage. Because this will also clear up some problems; e.g. you could have a non blocking initialize; but how is the caller expected to deal with the fact that a different thread might be initializing the object. There is protection against duplicate instantiation, but how can the second caller wait for the first one to complete? So an actual example usage is needed. Preferably a small working program. – pveentjer Jul 16 '21 at 03:55
  • when you say “an object that hasn't fully completed its initalization”, that's exactly what I'm confused. Can't we do this without a lock? Like the Sample3, when calling isInitialized( ) method returns true, as the "initialize()" execution happes-before “initCompleted.set(true)", which happens-before "initCompleted.get() == true", so may I say, then I will get "an object that has fully completed its initalization"? – R.h Jul 16 '21 at 03:58
  • There are many ways to solve this, but a lock is the simplest because it will cause the second thread to wait for the first one to complete. If you don't have a lock, then you either need to add polling or some kind of callback logic. And this can quickly spiral out of control from a complexity point of view. – pveentjer Jul 16 '21 at 04:01
  • Your InitializationSample2 will provide the blocking behavior. So I would focus on that one instead of on the non blocking versions. – pveentjer Jul 16 '21 at 04:02
  • Thanks a lot! I have updated the Sample3 and added some kind of usage. Please allow me not removing the other samples. – R.h Jul 16 '21 at 04:51