Why this synchronisation is not working in given scenario?

Archana Mundaye :
package singleton;

public class SingletonClass {

    private static SingletonClass singleton = null;

    private SingletonClass() {
    }

    static boolean stopThread = true;

    //approach 1 which fails in multithereaded env
    /*public static SingletonClass getInstance(){
        if(null == singleton){
            try {
                if(stopThread){
                    stopThread = false;
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            singleton = new SingletonClass();
        }
        return singleton;
    }*/

    //approach 2 which works
    //method is synchronized
   /* public static synchronized SingletonClass getInstance(){
        if(null == singleton){
                try {
                    if(stopThread){
                        stopThread = false;
                        Thread.sleep(1000);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                singleton = new SingletonClass();

        }
        return singleton;
    }*/

    ***//approach 3 which is failing but I don't understand why
   //big block of code is synchronized
    public static SingletonClass getInstance(){
        if(null == singleton){
            synchronized (SingletonClass.class){
                try {
                    if(stopThread){
                        stopThread = false;
                        Thread.sleep(1000);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                singleton = new SingletonClass();
            }
        }
        return singleton;
    }***


    //small block of code is synchronized, checked null again because even object instantiation is synchronised
    //if we don't check null, it will create new object once again
    //approach 4 which works
   /* public static SingletonClass getInstance(){
        if(null == singleton){
                try {
                    if(stopThread){
                        System.out.println("in thread...");
                        stopThread = false;
               //even if we interchange above 2 lines it makes whole lot of difference
               //till the time it takes to print "in thread"
               //2nd thread reaches there n enters if(stopThread) block because
               //stopThread is still true because 1st thread spent time in writing that sentence and 
               //did not set stopThread = false by the time 2nd thread reached there
                        Thread.sleep(1000);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (SingletonClass.class){
                    System.out.println("in this block");
                    if(null == singleton){
                        singleton = new SingletonClass();
                    }
                }
        }
        return singleton;
    }*/

}


---------------------------------------------------------

package singleton;

public class ThreadUsage implements Runnable {

    @Override
    public void run() {
        SingletonClass singletonOne = SingletonClass.getInstance();
        System.out.println(singletonOne.hashCode());
    }
}

----------------------------------------------------------------

package singleton;

class ThreadUsageTest {

    public static void main(String[] args) {
        Runnable runnableOne = new ThreadUsage();
        Runnable runnableTwo = new ThreadUsage();
        new Thread(runnableOne).start();
        new Thread(runnableTwo).start();
    }
}
---------------------------------------------------------------------------

In approach 3, it's not giving same hashCode for 2 objects, I've kept both Thread.sleep as well as object instantiation under synchronised block so what I'm thinking is , 2nd thread should not even enter this block until 1st finishes, but it's still doing and creating 2nd object leading to diff hashCode. What am I mssing here? Could someone correct my understanding here ? If I check for null b4 object creation then it's working as expected but why would I need to check null again here because my entire code is under synchronised block?

if(null == singleton)
       singleton = new SingletonClass();
T.J. Crowder :

Here's one way that code (approach 3) ends up creating and returning two (or more) separate objects for the singleton:

  • Thread A enters the function and sees null for singleton
  • Thread B enters the function and sees null for singleton
  • Thread A enters the synchronized block
  • Thread B waits because it can't enter the synchronized block
  • Thread A assigns to singleton
  • Thread A exits the synchronized block
  • Thread A returns one object
  • Thread B enters the synchronized block
  • Thread B assigns to singleton
  • Thread B returns a different object

E.g., there's a gap between the null check and entering the synchronized block that follows it.

To solve it, just make getInstance a synchronized method and remove the synchronized block inside it:

public static synchronized SingletonClass getInstance() {
    if (instance == null) {
            singleton = new SingletonClass();
    }
    return singleton;
}

Or if you really want to avoid synchronization on subsequent calls, on Java 5 or later (which hopefully you're using!), declare singleton volatile and check again within the synchronized block:

private static volatile SingletonClass singleton;
// ...
public static SingletonClass getInstance() { // Only works reliably on Java 5 (aka 1.5) and later!
    SingletonClass instance = singleton;
    if (instance == null) {
        synchronized (SingletonClass.class) {
            instance = singleton;
            if (instance == null) {
                singleton = instance = new SingletonClass();
            }
        }
    }
    return instance;
}

That's the double-checked locking idiom. In Java 4 (aka 1.4) and earlier that wasn't necessarily reliable, but it is now (provided you use volatile on the member).

In a comment user2683814 asked a good question:

Could you explain the assignment to local variable before null check in the second code snippet ? Checking the class variable directly won’t work ?

Yes, it would work, but less efficiently.

In cases where singleton is non-null, using a local means the method only accesses singleton once. If the code didn't use a local, it would access singleton at least twice (once to check it, once to return it). Since accessing a volatile variable is slightly expensive, better to use the local (which in the code above can be optimized into a register).

That may seem like premature micro-optimization, but if you weren't doing this in performance-critical code, you'd just make the method synchronized and avoid the complexity of double-checked locking entirely. :-)

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=418501&siteId=1