Menu Close

Double Checked Locking in Java

In this post, we’ll be taking a look at some techniques of creating a Singleton object in RxJava. Most importantly, we’ll be learning about double checked locking in java.

Singleton pattern in java is a creational pattern. Over time there have been concerns about the usage and implementation of Singleton pattern. This results from some very fundamental problems with the way singletons are implemented and used.

 

Singleton Pattern in Java

Singleton pattern in java has various features such as:

  1. Ensures that only one instance of the class lives inside the JVM.
  2. Provides global access to the instance of the class.
  3. A private constructor to prevent creating an instance of the class directly.
  4. Best used for logging, thread pool, caching etc…

There are three basic ways of creating a Singleton pattern in Java. I’ll list out all of them and tell you how the singleton pattern has evolved over time and why double checked locking is the best currently.

 

Basic

Here is a basic implementation of Singleton pattern in Java.

class Example{
    
  private Example mExample = null;
  
  public Example getInstance (){
    if (mExample == null)
      mExample = new Example ();
    return mExample;
  }
  // rest of the code...
}

Note: The constructor would be private in all implementations.

This code would fail in a multi-threaded context. Multiple threads could call the getInstance() method and end up creating multiple instances of the Singleton. This is undesired behaviour. Basic property of Singleton is that there should only be a single instance of the class in JVM.

Advantages:

  • Easy to read.
  • Would work fine in a single threaded application.

Disadvantages:

  • Would fail in multi-threaded context.
  • Multiple threads can create multiple instances of this class.
  • Would fail the purpose of Singletons.

 

Keep it Synchronized Silly

Some smart people came up with an elegant solution of creating singletons. We use the synchronized keyword to keep the threads from accessing the getInstance() method at the same time.

class Example{
    
  private Example mExample = null;
  
  public synchronized Example getInstance (){
    if (mExample == null)
      mExample = new Example ();
    return mExample;
  }
  // rest of the code...
}

By using the synchronized keyword, we’re JVM to let only one field access this method at one time. This solves our problem with multi-threaded contexts.

 

But this is not ideal!

If you have a look at the code above, you’ll notice that we’ve made the entire method synchronized. Every thread accessing the method would acquire a lock first.

Synchronization, or obtaining locks is an expensive method. It can really slow down the performance of your app. If you want to know more about performance overheads of synchronization, this SO answer would be a good start.

Even though all the threads acquire the lock, it’s only the first thread that needs locking. Once the object is initialized, null check is sufficient to maintain a single instance across threads.

Advantages:

  • Handles multi-threaded environment really well.
  • Easy to understand.

Disadvantages:

  • Acquires unnecessary lock every time a thread tries to access the method.
  • Locking is really expensive and with many threads candidating for acquiring a lock, this can lead to serious performance overheads.

Double Checked Locking

In the previous method, we synchronized the entire method to be thread safe. But synchronization doesn’t only works with methods. We can create synchronized blocks as well.

In this method we’ll be creating a synchronized block instead of an entire method.

class Example{
    
  private Example mExample = null;
  
  public Example getInstance (){
    if(mExample == null){
        synchronized(Example.class){
            if (mExample == null)
                mExample = new Example ();
        }
    }
    return mExample;
  }
  // rest of the code...
}

Here’s the sequence of steps:

  • First thread calls the getInstance() method.
  • It checks if the instance is null (for first thread, it is).
  • It then acquires a lock.
  • Checks if the field is still null? 
  • If it is then, it creates a new instance of the class and initializes the field. Finally, the instance is returned.
  • Rest of the threads don’t need to acquire the lock as field has already been initialized, hence lowering the synchronization hits!

Notice the multiple null checks before and after the synchronized block. Hence the name double checked locking.

Advantages:

  • Works in a multi-threaded environment.
  • Has much better performance than synchronized method.
  • Only the first thread needs to acquire the lock.
  • Best of the above methods.

Disadvantages:

  • Double null checks can be confusing at first.
  • Doesn’t work!!

double check locking

Wait what, it doesn’t work?!

Yes, there’s a subtle problem with the above method. It doesn’t always work.

The problem is that the compiler sees the programs very differently than the human eye. According to our logic, first, the instance of Example class should be created and then assigned to the mExample field.

But this order of operation isn’t guaranteed. Compilers are free to reorder your statements as long as it doesn’t affect the final result.

So, for instance, you could end up with a partially initialized object being assigned to mExample field. Then the other threads see the object as not-null. This results in threads using the partially initialized objects which can lead to a crash!

Compilers today do certain optimizations to your code in which they’re free to reorder the statements. The reordering can occur when compiler inlines the constructor call.

Doug Lea has written a detailed post on compiler-based reorderings

Paul Jakubik found an example of a use of double-checked locking that did not work correctly.

 

So, what do we do now?

If all the above methods are prone to fail, what do we have left?

In J2SE 5.0 the memory model of Java changed quite a bit. The volatile keyword now solves the problem above.

The java platform will not allow volatile field’s read or write to be re-ordered with any previous read or write.

class Example{
    
  private volatile Example mExample = null;
  
  public Example getInstance (){
    if(mExample == null){
        synchronized(Example.class){
            if (mExample == null)
                mExample = new Example ();
        }
    }
    return mExample;
  }
  // rest of the code...
}

Beware: This only works from JDK 5 and above. For android developers you’re good to go as Android uses Java 7 and above.

Conclusion

Hope you found this article useful. If you did, let me know in the comments section below, I’ll love to write more such conceptual articles.

*Important*: Join the AndroidVille SLACK  workspace for mobile developers where people share their learnings about everything latest in Tech, especially in Android Development, RxJava, Kotlin, Flutter, and mobile development in general.

Click on this link to join the workspace. It’s absolutely free!

Like what you read? Don’t forget to share this post on FacebookWhatsapp, and LinkedIn.

You can follow me on LinkedInQuoraTwitter, and Instagram where I answer questions related to Mobile Development, especially Android and Flutter.

If you want to stay updated with all the latest articles, subscribe to the weekly newsletter by entering your email address in the form on the top right section of this page.