~/software-design/single-pattern-thread-safe-reflection-free.md

Singleton Pattern with Thread-safe and Reflection-safe

4 min readOctober 31, 2019by Gorav Singal#design-patterns#creational-patterns
// 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.

$ subscribe --weekly

One deep dive a week, in your inbox

System design, AI engineering, trading systems and interview prep. Tested code, real numbers. Free, and you can unsubscribe any time.

or read it on gyanbyte.substack.com

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…

The Transactional Outbox Pattern: Never Lose an Event Again

The Transactional Outbox Pattern: Never Lose an Event Again

This is one of those patterns that looks like nothing until you get burnt once…

Event Sourcing, Explained: Store What Happened, Not Just the Final State

Event Sourcing, Explained: Store What Happened, Not Just the Final State

Here is a question I like to ask in interviews: your bank account shows a…

Change Data Capture (CDC): Stream Every Database Change Without Touching Your App

Change Data Capture (CDC): Stream Every Database Change Without Touching Your App

Your orders live in Postgres. But you also need them in Elasticsearch for search…

CQRS Pattern, Explained Simply: Split Your Writes From Your Reads

CQRS Pattern, Explained Simply: Split Your Writes From Your Reads

Most of us start our career with one model for everything. The same class, the…

The Circuit Breaker Pattern: Fail Fast, So One Dead Service Doesn't Kill Them All

The Circuit Breaker Pattern: Fail Fast, So One Dead Service Doesn't Kill Them All

Like the electrical switch in your home that it is named after, a circuit…

Latest Posts

How to Stay Valuable as a Software Engineer in the AI Era: A Practical Playbook

How to Stay Valuable as a Software Engineer in the AI Era: A Practical Playbook

In the previous article I argued that AI is not killing software engineering…

How to Review AI-Generated Code: A Checklist for Catching What the Model Missed

How to Review AI-Generated Code: A Checklist for Catching What the Model Missed

A few months back I approved a PR that was almost entirely AI-generated. It…

How to Debug AI-Generated Code: Understand First, Fix Last

How to Debug AI-Generated Code: Understand First, Fix Last

In the last article I wrote about reviewing AI code before it goes in. This one…

The Future of Software Engineering: What AI Changes, What Stays, and Where the Jobs Go

The Future of Software Engineering: What AI Changes, What Stays, and Where the Jobs Go

Every few weeks somebody sends me a scary headline — “AI will replace all…

The Transactional Outbox Pattern: Never Lose an Event Again

The Transactional Outbox Pattern: Never Lose an Event Again

This is one of those patterns that looks like nothing until you get burnt once…

Event Sourcing, Explained: Store What Happened, Not Just the Final State

Event Sourcing, Explained: Store What Happened, Not Just the Final State

Here is a question I like to ask in interviews: your bank account shows a…