JavaRush /Java Blog /Random EN /Design Patterns: Singleton

Design Patterns: Singleton

Published in the Random EN group
Hello! Today we will take a closer look at different design patterns, and we will start with the Singleton pattern, which is also called “singleton”. Design Patterns: Singleton - 1Let's remember: what do we know about design patterns in general? Design patterns are best practices that can be followed to solve a number of known problems. Design patterns are generally not tied to any programming language. Take them as a set of recommendations, following which you can avoid mistakes and not reinvent your wheel.

What is a singleton?

A singleton is one of the simplest design patterns that can be applied to a class. People sometimes say “this class is a singleton,” meaning that this class implements the singleton design pattern. Sometimes it is necessary to write a class for which only one object can be created. For example, a class responsible for logging or connecting to a database. The Singleton design pattern describes how we can accomplish such a task. A singleton is a design pattern that does two things:
  1. Provides a guarantee that a class will have only one instance of the class.

  2. Provides a global access point to an instance of this class.

Hence, there are two features that are characteristic of almost every implementation of the singleton pattern:
  1. Private constructor. Restricts the ability to create class objects outside of the class itself.

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

Implementation options

The singleton design pattern is used in different ways. Each option is good and bad in its own way. Here, as always: there is no ideal, but you need to strive for it. But first of all, let's define what is good and what is bad, and what metrics influence the evaluation of the implementation of a design pattern. Let's start with the positive. Here are the criteria that give the implementation juiciness and attractiveness:
  • Lazy initialization: when a class is loaded while the application is running exactly when it is needed.

  • Simplicity and transparency of the code: the metric, of course, is subjective, but important.

  • Thread safety: works correctly in a multi-threaded environment.

  • High performance in a multi-threaded environment: threads block each other minimally or not at all when sharing a resource.

Now the cons. Let's list the criteria that show the implementation in a bad light:
  • Non-lazy initialization: when a class is loaded when the application starts, regardless of whether it is needed or not (a paradox, in the IT world it is better to be lazy)

  • Complexity and poor readability of the code. The metric is also subjective. We will assume that if blood comes from the eyes, the implementation is so-so.

  • Lack of thread safety. In other words, “thread hazard”. 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.

Code

Now we are ready to consider various implementation options, listing the pros and cons:

Simple Solution


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

  • Thread safety

  • High performance in a multi-threaded environment

Minuses:
  • Not lazy initialization.
In an attempt to correct the last flaw, we get implementation number two:

Lazy Initialization


public class Singleton {
  private static Singleton INSTANCE;

  private Singleton() {}

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

Minuses:
  • Not thread safe

The implementation is interesting. We can initialize lazily, but we have lost thread safety. No problem: in implementation number three we synchronize everything.

Synchronized Accessor


public class Singleton {
  private static Singleton INSTANCE;

  private Singleton() {
  }

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

  • Thread safety

Minuses:
  • Poor performance in a multi-threaded environment

Great! In implementation number three, we brought back thread safety! True, it’s slow... Now the method getInstanceis synchronized, and you can only enter it one at a time. In fact, we do not need to synchronize the entire method, but only that part of it in which we initialize a new class object. But we can't simply wrap synchronizedthe part responsible for creating a new object in a block: this will not provide thread safety. It's a little more complicated. The correct synchronization method is given below:

Double Checked Locking


public class Singleton {
    private static 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

Minuses:
  • Not supported on Java versions lower than 1.5 (the volatile keyword was fixed in version 1.5)

I note that for this implementation option to work correctly, one of two conditions is required. The variable INSTANCEmust be either final, or volatile. The last implementation we'll discuss today is Class Holder Singleton.

Class Holder Singleton


public class Singleton {

   private Singleton() {
   }

   private 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.

Minuses:
  • For correct operation, it is necessary to guarantee that the class object Singletonis initialized without errors. Otherwise, the first method call getInstancewill end in an error ExceptionInInitializerError, and all subsequent ones will fail NoClassDefFoundError.

The implementation is almost perfect. And lazy, and thread-safe, and fast. But there is a nuance described in the minus. Comparison table of various implementations of the Singleton pattern:
Implementation Lazy initialization Thread safety Multithreading speed When to use?
Simple Solution - + Fast Never. Or when lazy initialization is not important. But never better.
Lazy Initialization + - Not applicable Always when multithreading is not needed
Synchronized Accessor + + Slowly Never. Or when the speed of work with multithreading does not matter. But never better
Double Checked Locking + + Fast In rare cases when you need to handle exceptions when creating a singleton. (when Class Holder Singleton is not applicable)
Class Holder Singleton + + Fast Always when multithreading is needed and there is a guarantee that a singleton class object will be created without problems.

Pros and cons of the Singleton pattern

In general, the singleton does exactly what is expected of it:
  1. Provides a guarantee that a class will have only one instance of the class.

  2. Provides a global access point to an instance of this class.

However, this pattern has disadvantages:
  1. Singleton violates the SRP (Single Responsibility Principle) - the Singleton class, in addition to its immediate responsibilities, also controls the number of its copies.

  2. The dependency of a regular class or method on a singleton is not visible in the class's public contract.

  3. Global variables are bad. The singleton eventually turns into one hefty global variable.

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

OK it's all over Now. We looked at the singleton design pattern. Now, in a conversation for life with your programmer friends, you will be able to say not only what is good about it, but also a few words about what is bad about it. Good luck in mastering new knowledge.

Additional reading:

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