DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
  1. DZone
  2. Software Design and Architecture
  3. Integration
  4. Singleton Pattern: Why Your Singleton Might not Be a Singleton

Singleton Pattern: Why Your Singleton Might not Be a Singleton

Want to learn more about what you are doing wrong with your singleton pattern? Check out this post to learn more about common mistakes with the singleton design pattern.

Aanand Roy user avatar by
Aanand Roy
·
Sep. 12, 18 · Analysis
Like (3)
Save
Tweet
Share
15.31K Views

Join the DZone community and get the full member experience.

Join For Free

The singleton pattern is definitely the easiest pattern of all the design patterns, but trust me — many of us are doing wrong. So, let’s find out more about this pattern.

We all know the motivation of using the Singleton pattern. It’s a pattern that allows us to have only a single instance of the object throughout the life-cycle of the application. Why would that be important? Because first, you don’t want to create more than one heavy resource consuming objects when you really want only one. Second, sometimes, not only creating more than one instance is costly, it can take you to an inconsistent state. For example, you wouldn’t want to have multiple database objects because changes in one may make others inconsistent.

But, you might be thinking, if I just need one object and I want to access it from anywhere, why can’t I make it a global variable? The answer is, yes, you can make it a global variable. But, what if that object creation requires heavy resource usage? Global variables might be created just when your application starts. You wouldn’t want too much of startup time for your Android app, right? These small things are easily noticeable in the mobile world. To prevent this, we make use of lazy-loading, which means that we need to create the object only when it is needed. We’ll see that in a second.

Now that we are clear why we want a singleton object, let’s see the characteristics of a singleton object.

A singleton class shouldn’t allow other classes to instantiate it. That means, no class should be able to do new Singleton() at all. How do we prevent this? This one is simple. Just make the constructor of the singleton class private . This simple step makes sure that the object won’t be instantiated anywhere — except within the same class. Private constructors can be called from the same class. This is true for private methods and fields, too. Below is a simple example in Kotlin of a class with private constructors.

class Singleton private constructor()


A singleton class should be accessible to the class. Even though we have restricted instantiation of the singleton class, we can still have access to that kind of object. This is by making a static method, which returns the singleton object. In the following example, we have made a static method that checks to see if the instance is null. If so, it will instantiate a new object and assign it to the INSTANCE variable, which will be returned on subsequent calls to the getInstance method.

class Singleton private constructor(){
  companion object {       
    private var INSTANCE: Singleton ? = null
      fun getInstance(): Singleton{ 
      if(INSTANCE == null){   
        INSTANCE = Singleton()    
      }           
      return INSTANCE!!     
    }    
  }
}


Most of the people who implement the singleton pattern get the above two concepts right. But, the problem with the above code is that it may produce more than one instance of your singleton class in a multi-threaded environment.

fun getInstance(): Singleton{     
  if(INSTANCE == null){       
    // <--- Imagine Thread 2 is here
            INSTANCE = Singleton() // <--- Imagine Thread 1 is here
        }...


In the above code, both Thread 1 and Thread 2 will produce two distinct objects, defeating the purpose of a singleton class. This can be catastrophic to your application and might be difficult to debug, because as with other multi-threaded issues, they only happen in some circumstances.

So, an easy and no-brainer fix would be to just execute the code within a synchronized lock. That would go like this:

fun  getInstance(): Singleton {  
  synchronized(this) {    
    if(INSTANCE == null){   
      INSTANCE = Singleton()    
    }       
    return INSTANCE!!
  }          
}


The above code will solve the case of the creation of multiple instances, but it may pose a performance problem. This means, even after correctly instantiating a singleton object, the subsequent access to it will be synchronized, which is not necessary. There is no issue in reading an object in a multi-threaded environment. A good ballpark metric would be to consider that any block under synchronized code-block will slow down the execution of that block by a factor of 100. If you are OK with this cost and you don’t need to access that singleton object too often, you can stop here. But, if you do need to access it multiple times, it would help if you optimize it.

To optimize it, let’s think what we actually need to optimize. We need to optimize only the object creation flow — not the reading of the object flow.

After the INSTANCE variable is created, synchronizing the flow is totally an unneeded overhead.

An easy fix here would be to create the object eagerly rather than lazily. Here’s what it would look like:

class Singleton private constructor(){  
  companion object {       
    val INSTANCE = Singleton()  
      fun  getInstance(): Singleton {
      return INSTANCE             
    }  
  }
}


In the above case, JVM will make sure that any thread accesses the INSTANCE variable after it has been created. But, then again, as we discussed, it makes it add up to your startup time where you are creating an object, even though you’d need to later or won’t need it ever.

So, we will now take a look at “double-checked locking,” which can help us overcome all the above issues that we’ve been talking about. In double-check locking, we’ll first see if the object is created or not. If not, then we will apply for synchronization.

companion object { 
  @Volatile private var INSTANCE: Singleton ? = null 
    fun  getInstance(): Singleton {  
    if(INSTANCE == null){            
      synchronized(this) {         
        INSTANCE = Singleton()     
      }          
    }         
    return INSTANCE!! 
  }
}


As you can see, we are only applying synchronization during the object instantiation phase.

You might be thinking, what is that @Volatile annotation doing there. It’s the same as the volatile keyword in Java. As you know, each thread has its copy of variables in its stack, and all the accessible values are copied to its stack when the thread is created. Adding a volatile keyword is like an announcement that says, “The value of this variable might change in some other thread.” Adding volatile ensures that the value of the variable is refreshed. Otherwise, it may so happen that the thread never updates its local cache.

As a final refactor, I’d like to make a small change and leverage Kotlin’s language construct. I really don’t like the screaming !! in the return statement.

companion object {   
  @Volatile private var INSTANCE: Singleton ? = null     
    fun  getInstance(): Singleton {      
    return INSTANCE?: synchronized(this){ 
      Singleton().also {            
        INSTANCE = it           
      }        
    }   
  }
}


So, there you have it— the perfect way to make a singleton. Your implementation may differ based on other considerations, like your application not running on a multi-threaded environment or you are fine with the performance cost of putting synchronize keyword over the entire method.

Thanks for reading! 

Here are a few other articles that might interest you:

  • Learn in 2 minutes: @JvmOverloads in Kotlin
  • Daily Kotlin: Static methods
  • RxJava — Flowables — What, when and how to use it?
  • RxJava — Schedulers — What, when and how to use it?
Singleton pattern Threading

Published at DZone with permission of Aanand Roy, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • How To Choose the Right Streaming Database
  • Specification by Example Is Not a Test Framework
  • Software Maintenance Models
  • Solving the Kubernetes Security Puzzle

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: