The singleton pattern in Java may seem like a very simple design pattern, but in fact, there are all kinds of unwieldy patterns that can be created. Singleton patterns can be implemented in different ways, and it is difficult to find a perfect one. Today I’m going to share some common implementations of singleton patterns and some of the problems they have.

I’ve written about the singleton pattern before, but that’s the simplest way and leaves out a lot of cases. Let’s look at it in more detail. Here’s the example of “a shop can only have one boss” to create a boss class singleton.

1. General implementation

(1) the hungry

This is the method described in the last blog post, and the simplest way to do it:

package com.example.singleinstance.eager;

import lombok.Getter;
import lombok.Setter;

/** ** /
@Getter
@Setter
public class Master {

   /** * name */
   private String name;

   /** ** only singleton */
   private static Master instance = new Master();

   /** * privatize constructor */
   private Master(a) {}/** * get the boss's unique singleton **@returnBoss only singleton */
   public static Master getInstance(a) {
      returninstance; }}Copy the code

The hunger-singleton pattern is called hunger-singleton because it initializes a unique singleton when the class is loaded.

The pros and cons of this approach are also obvious:

  • Advantages: High execution efficiency, absolute thread safety
  • Cons: You might not need this singleton, but it’s initialized anyway and could be a “dog in the manger”, wasting memory

So if we want to improve performance, we need to make some changes.

(2) LanHanShi

Lazy singletons are initialized when external access is made to the singleton:

package com.example.singleinstance.lazy;

import lombok.Getter;
import lombok.Setter;

/** * lazy singleton */
@Getter
@Setter
public class Master {

   /** * name */
   private String name;

   /** * unique singleton, not initialized */
   private static Master instance = null;

   /** * privatize constructor */
   private Master(a) {}/** * get the boss's unique singleton **@returnBoss only singleton */
   public static Master getInstance(a) {
      // If it is not initialized, initialize it
      if (instance == null) {
         instance = new Master();
      }
      returninstance; }}Copy the code

If it is the first call, initialize it. If it is the first call, return the singleton.

2. Find a way to break the singleton pattern

(1) Multithreading destroys singleton mode

The lazy singleton pattern does optimize performance, but it is not thread-safe. Given that n threads are accessing the singleton’s getInstance method at the same time for a very short period of time, it is possible that more than one thread will simultaneously determine that the singleton is null, resulting in multiple Master instances being initialized.

When we instantiate a singleton, we print out the address of the singleton and change getInstance to the following:

public static Master getInstance(a) {
   // If it is not initialized, initialize it
   if (instance == null) {
      instance = new Master();
      System.out.println(instance);
   }
   return instance;
}
Copy the code

Then create two threads and use the thread debugging mode of IDEA to interfere with the execution order of threads to simulate the simultaneous execution of two threads:

for (int i = 0; i < 2; i++) {
   new Thread(() -> {
      Master master = Master.getInstance();
   }).start();
Copy the code

Here break point and right-click breakpoint-thread mode:

To debug, you can manually switch the thread in the debug console and control its execution:

Using the step button (F5), let thread 0 enter the if statement and stop at the instantiation:

Switch to thread 1 and let thread 1 enter the if statement and stop at the instantiation:

Let the two threads finish, and you can see that the console outputs two different addresses:

As you can see, slacker style is not entirely thread-safe.

In this case, we can lock the getInstance method to achieve thread safety:

public synchronized static Master getInstance(a) {
   // If it is not initialized, initialize it
   if (instance == null) {
      instance = new Master();
   }
   return instance;
}
Copy the code

In this way, it is indeed thread safety, but it is always locked, the performance of the program will have a certain impact, that is not a better way to it?

If we think about it from the class initialization point of view, we can solve these problems with inner classes. In Java, an inner class is delayed-loaded, meaning that it loads when you use it, not when you use it, and is not affected by the external class. With this inner class feature, can we put singletons inside inner classes? Let’s try it:

package com.example.singleinstance.lazy;

import lombok.Getter;
import lombok.Setter;

/** * lazy inner class method singleton */
@Getter
@Setter
public class Master {

   /** * name */
   private String name;

   /** * privatize constructor */
   private Master(a) {}/** * gets the boss unique singleton. Final makes this method not allowed to be overridden or overridden@returnBoss only singleton */
   public static final Master getInstance(a) {
      // The inner class is loaded before the result is returned
      return InnerMaster.INSTANCE;
   }

   /** * the inner class of the boss class, which is not loaded until it is used
   private static class InnerMaster {
      private static final Master INSTANCE = newMaster(); }}Copy the code

This approach perfectly solves the memory problems of hungry singletons, as well as the performance problems of locking. An inner class is always initialized before a method call, and it is always initialized once (a class cannot be loaded more than once), thus avoiding thread-safety issues.

(2) Reflection destruction singleton mode

Constructors are indeed privatized, but their constructors can still be accessed using Java’s reflection mechanism:

// Use reflection to get the constructor and make it accessible
Constructor constructor = Master.class.getDeclaredConstructor();
constructor.setAccessible(true);
Master master1 = (Master) constructor.newInstance();
Master master2 = (Master) constructor.newInstance();
System.out.println(master1 == master2);
Copy the code

So even if we privatize the constructor, we can still double new it and get two instances, violating the basic principle of the singleton pattern.

To solve this problem, we can do some work in the constructor:

package com.example.singleinstance.lazy;

import lombok.Getter;
import lombok.Setter;

/** * lazy inner class method singleton */
@Getter
@Setter
public class Master {

   /** * name */
   private String name;

   /** * privatize constructor */
   private Master(a) {
      if(InnerMaster.INSTANCE ! =null) {
         throw new RuntimeException("Multiple instances are not allowed!"); }}/** * gets the boss unique singleton. Final makes this method not allowed to be overridden or overridden@returnBoss only singleton */
   public static final Master getInstance(a) {
      // The inner class is loaded before the result is returned
      return InnerMaster.INSTANCE;
   }

   /** * the inner class of the boss class, which is not loaded until it is used
   private static class InnerMaster {
      private static final Master INSTANCE = newMaster(); }}Copy the code

Run the above code again:

Ok, so here we have a further understanding: when a class is loaded, its inner classes are not loaded; When the class is used, its inner classes are loaded.

Note the difference between load and use here. Each class is loaded when the application starts, and you only use it when you call it to instantiate or call its methods.

(3) Serialization breaks the singleton pattern

Sometimes we need to serialize objects and transfer them over the network, and then deserialize them. As you all know, the deserialized object is not the original object, which also breaks the singleton pattern principle.

First let the Master class use the Serializable interface, then do the following test:

/ / the serialization
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(Master.getInstance());
// deserialize
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
Master master = (Master) ois.readObject();
System.out.println(master == Master.getInstance());
Copy the code

As you can see, singletons are broken with serialization.

We just need to add a readResolve method to the Master class:

package com.example.singleinstance.lazy;

import lombok.Getter;
import lombok.Setter;

import java.io.Serializable;

/** * lazy inner class method singleton */
@Getter
@Setter
public class Master implements Serializable {

   /** * name */
   private String name;

   /** * privatize constructor */
   private Master(a) {
      if(InnerMaster.INSTANCE ! =null) {
         throw new RuntimeException("Multiple instances are not allowed!"); }}/** * gets the boss unique singleton. Final makes this method not allowed to be overridden or overridden@returnBoss only singleton */
   public static final Master getInstance(a) {
      // The inner class is loaded before the result is returned
      return InnerMaster.INSTANCE;
   }

   private Object readResolve(a) {
      return InnerMaster.INSTANCE;
   }

   /** * the inner class of the boss class, which is not loaded until it is used
   private static class InnerMaster {
      private static final Master INSTANCE = newMaster(); }}Copy the code

Run again:

This seems pretty amazing: why is this method ok? It actually has to do with the execution logic of the ObjectInputStream class. You can go to study the JDK source code to know, again not too much verbose.

In fact, this method does guarantee that only one singleton is returned, but there are still multiple singletons in memory.

Of course, there has to be a better way.

3. Registered singleton mode

As the name implies, the registered singleton pattern is to register an instance in a place and fetch it based on an identifier.

This is usually done in two ways.

(1) [Recommendation] Enumerated singleton mode

To implement the singleton pattern using enumerations, that is, to write singleton classes as enumerations, we modify the Master class as follows:

package com.example.singleinstance.enumerate;

import lombok.Getter;
import lombok.Setter;

@Getter
public enum Master {

   /** ** only boss instance */
   INSTANCE;

   /** * name */
   @Setter
   private String name;

   /** * get a unique instance of the boss class **@returnUnique instance of the boss class */
   public static Master getInstance(a) {
      returnINSTANCE; }}Copy the code

We all know that each enumeration in an enumeration class is an instance of that enumeration class, and that enumeration classes can write member variables and methods.

Are enumerations in an enumeration class singletons? Let’s try it.

A. Try to use reflex damage

Constructor constructor = Master.class.getDeclaredConstructor();
constructor.setAccessible(true);
Master master = (Master) constructor.newInstance();
Copy the code

Results:

Visible reflection can’t find the constructor of an enumerated class because the constructor of an enumerated class is protected:

B. Try serialization destruction

/ / the serialization
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(Master.getInstance());
// deserialize
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
Master master = (Master) ois.readObject();
System.out.println(master == Master.getInstance());
Copy the code

Results:

This also takes advantage of the JDK’s deserialization mechanism, which means that an enumerated type actually finds a unique object by class name and class object and won’t be loaded multiple times by the class loader.

This can also be seen: Enumerated values are singletons by nature, which fits well with the singleton pattern idea.

(2) Container singleton

We can also use Map to create a singleton container to put instances in:

package com.example.singleinstance;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/** * singleton container */
public class SingleContainer {

   private SingleContainer(a) {}// The container that holds all singletons. The key is the fully qualified name of the class and the value is the corresponding singleton
   private static Map<String, Object> container = new ConcurrentHashMap<>();

   /** * get the single instance of the corresponding class, create ** if it does not exist@paramClassName The fully qualified name of the class *@returnSingle instance * /
   public synchronized static Object getInstance(String className) throws Exception {
      if(! container.containsKey(className)) { Object instance = Class.forName(className).getConstructor().newInstance(); container.put(className, instance);return instance;
      }
      returncontainer.get(className); }}Copy the code

This approach also seems advanced, but it also creates threading problems.

4,

So while the singleton pattern looks simple, it’s actually very difficult to write a rigorous, watertight singleton pattern.

For daily development, the lazy singleton pattern or enumerated singleton pattern based on inner classes is recommended. Sample code for both is shown below:

Lazy singletons based on inner classes:

package com.example.singleinstance.lazy;

import lombok.Getter;
import lombok.Setter;

/** * lazy inner class method singleton */
@Getter
@Setter
public class Master {

   /** * name */
   private String name;

   /** * privatize constructor */
   private Master(a) {
      if(InnerMaster.INSTANCE ! =null) {
         throw new RuntimeException("Multiple instances are not allowed!"); }}/** * gets the boss unique singleton. Final makes this method not allowed to be overridden or overridden@returnBoss only singleton */
   public static final Master getInstance(a) {
      // The inner class is loaded before the result is returned
      return InnerMaster.INSTANCE;
   }

   private Object readResolve(a) {
      return InnerMaster.INSTANCE;
   }

   /** * the inner class of the boss class, which is not loaded until it is used
   private static class InnerMaster {
      private static final Master INSTANCE = newMaster(); }}Copy the code

Enumerated singleton pattern:

package com.example.singleinstance.enumerate;

import lombok.Getter;
import lombok.Setter;

@Getter
public enum Master {

   /** ** only boss instance */
   INSTANCE;

   /** * name */
   @Setter
   private String name;

   /** * get a unique instance of the boss class **@returnUnique instance of the boss class */
   public static Master getInstance(a) {
      returnINSTANCE; }}Copy the code