User Professor Hans Noodles
Professor Hans Noodles
Level 41

Design patterns: Singleton

Published in the Java Developer group
6641 members
Hi! Today we'll dive into the details of various design patterns, starting with the singleton pattern. Design patterns: Singleton - 1 Let's review: what do we know about design patterns in general? Design patterns are best practices that we can apply to solve a number of known problems. Design patterns are generally not tied to any programming language. Think of them as a set of recommendations to help you avoid mistakes and avoid reinventing the wheel.

What is a singleton?

Singleton is one of the simplest class-level design patterns. Sometimes people say "this class is singleton”, which means that the class implements the singleton design pattern. Sometimes it is necessary to write a class where we restrict instantiation to a single object. For example, a class responsible for logging or connecting to a database. The singleton design pattern describes how we can achieve this. A singleton is a design pattern that does two things:
  1. It guarantees that there will only ever be one instance of the class.

  2. It provides a single point of global access to that instance.

Hence, there are two features that are characteristic of nearly every implementation of the singleton pattern:
  1. A private constructor. This limits the ability to create objects of the class outside of the class itself.

  2. A public static method that returns the instance of the class. This method is called getInstance. This is the point of global access to the class instance.

Implementation options

The singleton design pattern is applied in various ways. Each option is good and bad in its own way. As always, there is no perfect option here, but we should strive for one. First of all, let's decide what constitutes good and bad, and what metrics affect how we assess the various implementations of the design pattern. Let's start with the good. Here are factors that make an implementation more juicy and appealing:
  • Lazy initialization: the instance is not created until it is needed.

  • Simple and transparent code: this metric, of course, is subjective, but it is important.

  • Thread safety: correct operation in a multi-threaded environment.

  • High performance in a multi-threaded environment: little or no thread blocking when sharing a resource.

Now the cons. We'll list factors that put an implementation in a bad light:
  • No lazy initialization: when the class is loaded when the application starts, regardless of whether or not it is needed (paradoxically, in the IT world it is better to be lazy)

  • Complex and difficult-to-read code. This metric is also subjective. If your eyes start bleeding, we'll assume the implementation isn't the best.

  • Lack of thread safety. In other words, "thread danger". Incorrect operation in a multi-threaded environment.

  • Poor performance in a multi-threaded environment: threads block each other all the time or often when sharing a resource.

Design patterns: Singleton - 2

Code

Now we're ready to consider various implementation options and indicate the pros and cons:

Simple


public class Singleton {
    private static final Singleton INSTANCE = new Singleton();
    
    private Singleton() {
    }
    
    public static Singleton getInstance() {
        return INSTANCE;
    }
}
The simplest implementation. Pros:
  • Simple and transparent code

  • Thread safety

  • High performance in a multi-threaded environment

Cons:
  • No lazy initialization.
In an attempt to fix the previous shortcoming, we get implementation number two:

Lazy initialization


public class Singleton {
  private static final Singleton INSTANCE;

  private Singleton() {}

  public static Singleton getInstance() {
    if (INSTANCE == null) {
      INSTANCE = new Singleton();
    }
    return INSTANCE;
  }
}
Pros:
  • Lazy initialization.

Cons:
  • Not thread safe

This implementation is interesting. We can initialize lazily, but we've lost thread safety. No worries — we synchronize everything in implementation number three.

Synchronized access


public class Singleton {
  private static final Singleton INSTANCE;

  private Singleton() {
  }

  public static synchronized Singleton getInstance() {
    if (INSTANCE == null) {
      INSTANCE = new Singleton();
    }
    return INSTANCE;
  }
}
Pros:
  • Lazy initialization.

  • Thread safety

Cons:
  • Poor multithreaded performance

Excellent! In implementation number three, we restore thread safety! Of course, it's slow... Now the getInstance method is synchronized, so it can be executed by only one thread at a time. Rather than synchronize the entire method, we actually only need to synchronize the part of it that initializes the new instance. But we can't simply use a synchronized block to wrap the part responsible for creating the new instance. Doing that would not ensure thread safety. It's all a bit more complicated. Proper synchronization can be seen below:

Double-checked locking


public class Singleton {
    private static final Singleton INSTANCE;

  private Singleton() {
  }

    public static Singleton getInstance() {
        if (INSTANCE == null) {
            synchronized (Singleton.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Singleton();
                }
            }
        }
        return INSTANCE;
    }
}
Pros:
  • Lazy initialization.

  • Thread safety

  • High performance in a multi-threaded environment

Cons:
  • Not supported in earlier versions of Java. Below 1.5

Note that in order for this implementation option to work correctly, one of two conditions must be satisfied. The INSTANCE variable must be either final or volatile. The last implementation that we'll discuss today is the class holder singleton.

Class holder


public class Singleton {

   private Singleton() {
   }

   public static class SingletonHolder {
       public static final Singleton HOLDER_INSTANCE = new Singleton();
   }

   public static Singleton getInstance() {
       return SingletonHolder.HOLDER_INSTANCE;
   }
}
Pros:
  • Lazy initialization.

  • Thread safety.

  • High performance in a multi-threaded environment.

Cons:
  • Correct operation requires a guarantee that the singleton object is initialized without errors. Otherwise, the first call to the getInstance method will result in an ExceptionInInitializerError, and all subsequent calls will produce a NoClassDefFoundError.

This implementation is almost perfect. It is lazy, and thread safe, and fast. But it has a nuance, as explained in the list of cons. Comparison of various implementations of the singleton pattern:
Implementation Lazy initialization Thread safety Multithreaded performance When to use?
Simple - + Fast Never. Or possibly when lazy initialization is not important. But never would be better.
Lazy initialization + - Not applicable Always when multithreading is not needed
Synchronized access + + Slow Never. Or possibly when multithreaded performance does not matter. But never would be better.
Double-checked locking + + Fast In rare cases when you need to handle exceptions when creating the singleton (when the class holder singleton is not applicable)
Class holder + + Fast Whenever multithreading is needed and there is a guarantee that the singleton object will be created without problems.

Pros and cons of the singleton pattern

In general, a singleton does exactly what is expected of it:
  1. It guarantees that there will only ever be one instance of the class.

  2. It provides a single point of global access to that instance.

However, this pattern has shortcomings:
  1. A singleton violates the single responsibility principle: in addition to its direct duties, the singleton class also controls the number of instances.

  2. An ordinary class's dependence on a singleton is not visible in the class's public contract.

  3. Global variables are bad. Ultimately, a singleton turns into a hefty global variable.

  4. The presence of a singleton reduces the testability of the application as a whole and the classes that use the singleton in particular.

And that's it! :) We've explored the singleton design pattern with you. Now, for the rest of your life, when conversing with your programmer friends, you can mention not only how good the pattern is, but also a few words about what makes it bad. Good luck mastering this new knowledge.

Additional reading:

Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION