Singleton Pattern

Singleton Pattern is one of the simplest design patterns in Java. This type of design pattern belongs to creational patterns, and it provides an optimal way to create objects.

This pattern involves a single class that is responsible for creating its own object while ensuring that only a single object is created. This class provides a way to access its unique object directly, without needing to instantiate an object of the class.

The singleton pattern is a creational design pattern that ensures a class has only one instance and provides a global access point to access that instance.

Note:

  • 1. A singleton class can only have one instance.
  • 2. The singleton class must create its own unique instance.
  • 3. The singleton class must provide this instance to all other objects.

summary

Singleton Pattern

intent

Ensure that a class has only one instance, and provide a global access point to access that instance.

Mainly Solves

The problem of frequently creating and destroying globally used class instances.

When to Use

When there is a need to control the number of instances and save system resources.

How to Solve

Check whether the singleton already exists in the system; if it exists, return that instance; if it does not exist, create a new instance.

Key code

The constructor is private.

Application example

  • A class has only one class teacher.
  • When Windows operates on files in a multi-process, multi-threaded environment, to avoid multiple processes or threads operating on one file at the same time, it needs to be handled through a unique instance.
  • The device manager is designed as a singleton pattern. For example, if a computer has two printers, it avoids printing the same file simultaneously.

Advantages

  • There is only one instance in memory, reducing memory overhead, especially when instances are frequently created and destroyed (such as managing the homepage page cache of a management school).
  • Avoid multiple occupation of resources (such as file write operations).

Disadvantages

  • No interface, cannot be inherited.
  • It conflicts with the Single Responsibility Principle; a class should only care about its internal logic, not the way it is instantiated.

Use Case

  • Generate unique serial numbers.
  • A counter in WEB development, avoiding the count being incremented in the database on every refresh; instead, it is cached first.
  • Creating objects that consume too many resources, such as I/O and database connections.

Notes

  • Thread Safety:getInstance()Synchronization locks need to be used in the method.synchronized (Singleton.class)To prevent multiple threads from entering at the same time causing the instance to be created multiple times.
  • Lazy initialization: The instance is created on the first callgetInstance()to the method.
  • Serialization and deserialization.: OverridereadResolveMethod to ensure that no new instance is created during deserialization.
  • Reflection Attack: Add guard code in the constructor to prevent creating new instances through reflection.
  • Class loader issue: Be aware of the multiple instance problems that can be caused by complex class loading environments.

Structure

The singleton pattern includes the following main roles:

  • Singleton class: a class that contains the singleton instance, usually declaring the constructor as private.
  • Static member variable: a static member variable used to store the singleton instance.
  • Instance retrieval method: a static method used to retrieve the singleton instance.
  • Private constructor: prevents external code from directly instantiating the singleton class.
  • Thread safety handling: ensures that the creation of the singleton instance is safe in a multi-threaded environment.

Implementation

We will create anSingleObjectclass.SingleObjectThe class has its private constructor and its own static instance.

SingleObjectThe class provides a static method for the outside world to obtain its static instance.SingletonPatternDemoClass usageSingleObjectclass to getSingleObjectObject.

单例模式的 UML 图

Step 1

Create a Singleton class.

SingleObject.java

public class SingleObject { //Create an object of SingleObject private static SingleObject instance = new SingleObject(); //Make the constructor private so that the class cannot be instantiated private SingleObject(){} //Get the only available object public static SingleObject getInstance(){ return instance; } public void showMessage(){ System.out.println("Hello World!"); } }

Step 2

Obtain the unique object from the singleton class.

SingletonPatternDemo.java

public class SingletonPatternDemo { public static void main(String[] args) { //Illegal constructor //Compile-time error: constructor SingleObject() is not visible //SingleObject object = new SingleObject(); //Get the only available object SingleObject object = SingleObject.getInstance(); //Display Message object.showMessage(); } }

Step 3

execution program, output result:

Hello World!

Several implementation approaches for the singleton pattern

There are multiple ways to implement the singleton pattern, as shown below:

1. Lazy mode, thread-unsafe

Whether Lazy initialization:Yes

Is it multithread-safe:no

Implementation difficulty:easy

Description:This is the most basic implementation. The biggest problem with this implementation is that it does not support multithreading. Because there is no synchronized lock, strictly speaking, it is not a singleton pattern.
This approach has obvious lazy loading, does not require thread safety, and cannot work properly in multithreading.

Example

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

The several implementation methods introduced next all support multithreading, but there are differences in performance.

2. Lazy mode, thread-safe

Whether Lazy initialization:Yes

Is it multithread-safe:Yes

Implementation difficulty:easy

Description:This approach has good lazy loading and can work well in multithreading, but its efficiency is very low, and synchronization is not needed in 99% of cases.
Advantages: initialized only on first call, avoiding memory waste.
Disadvantages: synchronized must be added to ensure singleton behavior, but locking affects efficiency.
The performance of getInstance() is not critical to the application (this method is not used frequently).

Example

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

3. Eager initialization

Whether Lazy initialization:no

Is it multithread-safe:Yes

Implementation difficulty:easy

Description:This approach is relatively common, but it can easily produce garbage objects.
Advantages: no locking, execution efficiency is improved.
Disadvantages: initialized at class loading time, wasting memory.
It avoids multithreading synchronization issues based on the classloader mechanism. However, the instance is instantiated when the class is loaded. Although there are many reasons that cause the class to be loaded, in the singleton pattern most of them are calls to the getInstance method, but it cannot be confirmed that there are no other ways (or other static methods) that cause the class to be loaded. In that case, initializing the instance obviously does not achieve the effect of lazy loading.

Example

public class Singleton { private static Singleton instance = new Singleton(); private Singleton (){} public static Singleton getInstance() { return instance; } }

4. Double-checked locking / double-checked locking (DCL, i.e., double-checked locking)

JDK Version:Since JDK 1.5

Whether Lazy initialization:Yes

Is it multithread-safe:Yes

Implementation difficulty:More complex

Description:This approach uses a double-lock mechanism, which is safe and maintains high performance in multithreading.
The performance of getInstance() is critical to the application.

Example

public class Singleton { private volatile static Singleton singleton; private Singleton (){} public static Singleton getSingleton() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }

5. Registry/Static Inner Class

Whether Lazy initialization:Yes

Is it multithread-safe:Yes

Implementation difficulty:Average

Description:This approach can achieve the same effect as double-checked locking, but it is simpler to implement. For lazy initialization of static fields, this approach should be used instead of double-checked locking. This approach only applies to static fields; double-checked locking can be used when instance fields require lazy initialization.
This approach also uses the classloader mechanism to ensure that only one thread initializes the instance. It differs from approach 3 in that: in approach 3, as long as the Singleton class is loaded, the instance will be instantiated (without achieving lazy loading), whereas in this approach, after the Singleton class is loaded, the instance is not necessarily initialized. Because the SingletonHolder class is not actively used, it is only when the getInstance method is explicitly called that the SingletonHolder class is explicitly loaded, thereby instantiating the instance. Imagine that if instantiating the instance consumes a lot of resources, you may want it to be lazily loaded. On the other hand, you may not want it to be instantiated when the Singleton class is loaded, because there is no guarantee that the Singleton class will not be actively used and loaded elsewhere. In that case, instantiating the instance at that time is obviously inappropriate. At this point, this approach is much more reasonable than approach 3.

Example

public class Singleton { private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton (){} public static final Singleton getInstance() { return SingletonHolder.INSTANCE; } }

6. Enumeration

JDK Version:Since JDK 1.5

Whether Lazy initialization:no

Is it multithread-safe:Yes

Implementation difficulty:easy

Description:This implementation approach has not been widely adopted, but it is the best method for implementing the singleton pattern. It is more concise, automatically supports the serialization mechanism, and absolutely prevents multiple instantiation.
This approach is advocated by Josh Bloch, the author of Effective Java. It not only avoids multithreading synchronization issues, but also automatically supports the serialization mechanism, prevents deserialization from re-creating new objects, and absolutely prevents multiple instantiation. However, since the enum feature was only added after JDK 1.5, writing it this way inevitably feels unfamiliar, and it is rarely used in actual work.
It cannot call the private constructor through a reflection attack.

Example

public enum Singleton { INSTANCE; public void whateverMethod() { } }

From experience:In general, it is not recommended to use the 1st and 2nd lazy approaches; the 3rd eager approach is recommended. Only when lazy loading needs to be explicitly implemented should the 5th registration approach be used. If deserialization is involved in creating objects, you can try the 6th enum approach. If there are other special requirements, you can consider the 4th double-checked locking approach.

other extensions