Is the following java code thread safe without volatile?
Asked Answered
B

3

3
public static Singleton singleton;

public static Singleton get(){
    synchronized (Singleton.class) {  
        if (singleton == null) {  
            singleton = new Singleton();  
        }  
    } 
    return singleton;
}

Some one say that no volatile for singleton variable is wrong. But I think this is the right code for singleton object creation. I want to know whether this code has a thread safety or not?

Bibulous answered 16/3, 2021 at 11:12 Comment(1)
This code is correctly synchronized. So it will work fine; however there are better performing alternatives if you do not want to pay the price for the entering the lock every time you obtain the value. That is where the double checked locking problems originated and then you quickly end up with a volatile.Eclair
G
4

As anatolyg pointed out, you should make the field singleton private to avoid unwanted non-thread-safe accesses to that field.

Furthermore, even though in:

public static Singleton get(){
    synchronized (Singleton.class) {  
        if (singleton == null) {  
            singleton = new Singleton();  
        }  
    } 
    return singleton;
} 

the return singleton; is outside of the synchronized block, this code is still thread-safe because the remaining code is inside the synchronized block, and consequently, all threads within that block will force a happens-before relation (i.e., there is no way threads will return null if the instance was properly set).

That being said beware that: quoting Holger

As long as the actual write to singleton happens-before the beginning of the synchronized block, everything works. It only works because there is at most one write, definitely performed before the return. If more writes were possible, they could happen concurrently to the return statement that is outside the synchronized block.

There is a complete SO Thread that tackles why it is thread-safe to leave the return singleton outside the synchronized block.

Notwithstanding, I share the same opinion of others users such as this one

since the return doesn't take any CPU or anything, there is no reason why it shouldn't be inside of the synchronized block. If it was then the method can be marked as synchronized if we are in the Singleton class here. This would be cleaner and better in case singleton gets modified elsewhere.

That being said you don't need the volatile clause because you are synchronizing the initialization of the variable singleton. In this context, the synchronized clause guarantees not only that multiple threads will not access:

    if (singleton == null) {  
        singleton = new Singleton();  
    }  

but also that each thread sees the most up-to-date reference of the singleton field. Consequently, the race-condition of multiple threads assigning different object instances to the singleton field will not happen.

Some one say that no volatile for singleton variable is wrong.

Probably this person is mistaken your code with the double-checked locking pattern, which is a performance optimization over the version that you have shown. In your version threads will synchronize every time they call the method get, which is not necessary after the variable singleton has been correctly initialized. This is the overhead that the double-checked locking pattern tries to avoid. To achieve that the volatile is needed (you can read an in-depth explanation on this SO Thread), more information about this double-checked locking pattern can be found here.

Gmt answered 16/3, 2021 at 13:25 Comment(0)
S
4

Almost. If you use the synchronized block for thread-safe access to a variable/field, the code is correct if you do both read and write under the same lock (synchronization on the same object's monitor). So, in your code you should prevent normal (with no any memory barriers) read by the "private" modifier instead of the "public" declaration.

private static Singleton singleton; // now we don't have direct access (to read/write) the field outside

public static Singleton get(){
    synchronized (Singleton.class) { // all reads in the synchronized Happens-Before all writes
        if (singleton == null) { // first read
            singleton = new Singleton(); // write
        }
    } 
    return singleton; // the last normal read
}

You may note that the last read return singleton; is normal, but it is subsequent (in Program Order) read after the first synchronized read (if singleton wasn't null) or write (if null) and doesn't require to be placed inside the synchronized block, since PO->HB for one single thread (https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4.5 "If x and y are actions of the same thread and x comes before y in program order, then hb(x, y)").

But from my point of view the following structure seems to be more idiomatic

// all access is under the lock
synchronized (Singleton.class) {  
    if (singleton == null) {
        singleton = new Singleton();
    }
    return singleton;
 }

or just

public static synchronized Singleton get() {
    if (singleton == null) {
        singleton = new Singleton();
    }
    return singleton;
}

Now the code is correct, but it may be not so efficient. This brings us to the to the Double-Checked Locking idiom. You can find a good review of the problem in https://shipilev.net/blog/2014/safe-public-construction/

BTW, sometimes even the following code may be OK:

private static volatile Singleton singleton;

public static Singleton get() {
    if (singleton == null) {
        singleton = new Singleton();
    }
    return singleton;
}

This code doesn't have a data race, but a race condition. It always return an instance of Singleton, but not always the same one. In other words, on the first calls of the get() we may see different instances of the Singleton returned back, which rewrites each other to the singleton field, but if you don't care :)... (small immutable/read-only singletons with the same state, for example)

Shumate answered 16/3, 2021 at 13:42 Comment(0)
L
-3

volatile is not needed for variable in your code.but your code has some performance flaw. everytime the code in get will run inside a synchronized block which causes little performance overhead. You can use the double checked locking mechanism to avoid the performance overhead. The below code shows a right way to create thread safe singleton in java with Double checked locking mechanism.

  class Singleton {
    private static volatile Singleton singleton = null;
    public static Singleton get() {
        if (singleton == null) {
            synchronized(this) {
                if (singleton == null)
                    singleton = new Singleton();
            }
        }
        return singleton;
    }
}

For more details visit this link and scroll to the bottom section "Fixing Double-Checked Locking using Volatile".

Lopeared answered 16/3, 2021 at 11:49 Comment(1)
It's not clear why you think their code could produce two instances.Torquay

© 2022 - 2024 — McMap. All rights reserved.