software-design4 Min Read

Singleton Pattern with Thread-safe and Reflection-safe

Gorav Singal

October 31, 2019

TL;DR

The simplest production-safe Singleton in Java is an enum — it's inherently thread-safe, serialization-safe, and reflection-proof. Double-checked locking works but is more complex for no real benefit.

Singleton Pattern with Thread-safe and Reflection-safe

What is a Singleton Pattern

Following constraints are applied:

  • Where we can restrict the number of instances of a class to just 1
  • No-one should be able to create another instance of the class
  • Should provide a way to get the instance of the method
  • Should be thread safe

Some add-on requirements

  • Should not degrade performance
  • Should also not work if someone instantiate using reflection (Very important actually)

Naive Singleton solution

Some basics around singleton implementation:

  • Need to make constructor as private, so that no-one from outside can call it
  • Need to provide a static method which will return the instance
  • Need to declare a static variable inside class of same type

Lets look at some naive solutions:

class Singleton {
      private static Singleton singleton = new Singleton();

      private Singleton() {}

      public static Singleton getInstance() {
            return singleton;
      }
}

Do you see any issue with above code?


Well, you are instantiating the class on load time itself. If during the lifetime of your code, nobody wants this instance. This instance will be kept in memory unused.

Lazy Initialization

Lets lazy-initialize it

class Singleton {
      private static Singleton singleton = null;

      private Singleton() {}

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

Do you see any issue with above code?


Assume you are working in multi-threaded environment. And, there might be a possibility that more than one thread call getInstance() method at the same time. So, all of them are going to see the instance variable as null. And, each one of them will be calling the constructor, and each will receive a new copy. This is the issue of thread safe.

Solving Thread Safety

class Singleton {
      private static Singleton singleton = null;

      private Singleton() {}

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

The most naive thread-safe solution is to make whole getInstance() method as synchronized. Synchronized keyword makes a method thread safe, but with extra overhead. Note: it will take a lock on complete class (static method) unnecessary, which is a costly operation. Whereas, we just need to protect the code where we are checking the instance with null.

Lets optimize it.

Optimized Thread Safe solution

class Singleton {
      private static Singleton singleton = null;

      private Singleton() {}

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

Above approach is called as Double Lock. For the very first time, when it is not instantiated. Threads might come at first check (instance == null), all of them will proceed. But, there comes the lock now. So, only one thread can enter the protected block. And, again we are checking for null. And, instantiating only when it is not initialized. After this thread exists the protected block. Other thread will enter. Now, it will not see this as null, so it will not instantiate it. The benefit we are getting from this is for further calls, we will never take lock after the instance is initialized.


Do you see any issue with above code?


Well, when the program is loaded into memory. OS and JVM performs bunch of optimizations. And, it allocates separate memory to each thread in the stack. There might be a possibility (for very small duration of time) that when one thread instantiates the class, it still remains in its private memory (stack). And, then another thread again instantiates (even after double lock, because it still sees it as null).

There is special keyword in java: volatile, which applies to declaration of variables.

It is an instruction to compiler that *Do not make any optimization to this thing, and make only one copy of this in memory, so that it will always reflect fresh state*

Use of Volatile

Now, take a look at the code.

class Singleton {
      private static volatile Singleton singleton = null;

      private Singleton() {}

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

The singleton class looks OK? Right?

Lets take a look at some advanced optimization. If you have used concept of *reflection* of java, you can still call its constructor. Basically you can access even private things of a class using reflection. So, all constaints goes for a toss?

Lets fix this.

Avoid instantiation via Reflection

class Singleton {
      private static volatile Singleton singleton = null;

      private Singleton() {
            if (instance != null) {
                  throw new RuntimeException("Use only getInstance() to get instance!");
            }
      }

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

Note the constructor definition now.

Pitfalls of Singleton Pattern

Lets look at some pitfalls of this pattern.

  • This pattern is often overused. This pattern is so simple and popular that people tend to use it where it is not required.
  • There are some performance issues associated with singleton pattern. If you wrongly use them.
  • Since there are private constructors and private instance variable. They are difficult to unittest.
  • Its very easy to make it thread-unsafe. If you are not careful, this mistake is very often.
  • People often get confused this with factory pattern.
Share

Related Posts

Principles of Software Designing

Principles of Software Designing

It is very easy to build a software or app. But, it is trickier to have a good…

Deep Dive on Redis: Architecture, Data Structures, and Production Usage

Deep Dive on Redis: Architecture, Data Structures, and Production Usage

“Redis is not just a cache. It’s a data structure server that happens to be…

Deep Dive on Apache Kafka: A System Design Interview Perspective

Deep Dive on Apache Kafka: A System Design Interview Perspective

“Kafka is not a message queue. It’s a distributed commit log that happens to be…

Deep Dive on Elasticsearch: A System Design Interview Perspective

Deep Dive on Elasticsearch: A System Design Interview Perspective

“If you’re searching, filtering, or aggregating over large volumes of semi…

Deep Dive on API Gateway: A System Design Interview Perspective

Deep Dive on API Gateway: A System Design Interview Perspective

“An API Gateway is the front door to your microservices. Every request walks…

REST API Design: Pagination, Versioning, and Best Practices

REST API Design: Pagination, Versioning, and Best Practices

Every time two systems need to talk, someone has to design the contract between…

Latest Posts

AI Video Generation in 2025 — Models, Costs, and How to Build a Cost-Effective Pipeline

AI Video Generation in 2025 — Models, Costs, and How to Build a Cost-Effective Pipeline

AI video generation went from “cool demo” to “usable in production” in 2024-202…

AI Models in 2025 — Cost, Capabilities, and Which One to Use

AI Models in 2025 — Cost, Capabilities, and Which One to Use

Choosing the right AI model is one of the most impactful decisions you’ll make…

AI Image Generation in 2025 — Models, Costs, and How to Optimize Spend

AI Image Generation in 2025 — Models, Costs, and How to Optimize Spend

Generating one image with AI costs between $0.002 and $0.12. That might sound…

AI Coding Assistants in 2025 — Every Tool Compared, and Which One to Actually Use

AI Coding Assistants in 2025 — Every Tool Compared, and Which One to Actually Use

Two years ago, AI coding meant one thing: GitHub Copilot autocompleting your…

AI Agents Demystified — It's Just Automation With a Better Brain

AI Agents Demystified — It's Just Automation With a Better Brain

Let’s cut through the noise. If you read Twitter or LinkedIn, you’d think “AI…

Supply Chain Security — Protecting Your Software Pipeline

Supply Chain Security — Protecting Your Software Pipeline

In 2024, a single malicious contributor nearly compromised every Linux system on…