Java singleton mode implementation, complete learning at one time, plus points in the interview

Singleton pattern is the most commonly used pattern in design patterns. It belongs to object creation mode, which can ensure that only one instance of a class is generated in the system. Such behavior can bring two benefits:

  • For frequently used objects, the time spent creating objects can be omitted, which is a very considerable overhead for those heavyweight objects.
  • Since the number of new operations is reduced, the frequency of system memory usage will also be reduced, which will reduce GC pressure and shorten GC pause time.

In practical applications, there are many times when we only need one object, such as thread pool, cache, registry, log object, etc. at this time, it is the best choice to design it as singleton mode.

1. Six implementation methods of singleton mode

1) Lazy mode (thread unsafe)

public class Singleton01 {

    private static Singleton01 instance;

    /**
     * Private construction method
     */
    private Singleton01(){}

    public static Singleton01 getInstance() {
        if(instance == null) {
            instance = new Singleton01();
        }

        return instance;
     }
}

This writing method realizes delayed loading, but the thread is not safe. No use!

2) Lazy mode (thread safe)

public class Singleton02 {

    private static Singleton02 instance;

    /**
     * Private construction method
     */
    private Singleton02(){}

    public static synchronized Singleton02 getInstance() {
        if(instance == null) {
            instance = new Singleton02();
        }

        return instance;
     }
}

This writing method realizes delayed loading and adds synchronized to ensure thread safety, but the efficiency is too low. Not recommended

3) Lazy mode (double check lock)

public class Singleton03 {

    private volatile static Singleton03 instance;

    /**
     * Private construction method
     */
    private Singleton03(){}

    public static Singleton03 getInstance() {
        if(instance == null) {
            synchronized (Singleton03.class) {
                if (instance == null) {
                    instance = new Singleton03();
                }
            }
        }

        return instance;
     }
}

volatile mechanism is used. This is an upgraded version of the second method, commonly known as double check locking. It ensures both efficiency and safety.

4) Hungry man model

public class Singleton03 {

    private static Singleton03 instance = new Singleton03();

    /**
     * Private construction method
     */
    private Singleton03(){}

    public static synchronized Singleton03 getInstance() {
        return instance;
     }
}

This class based loading mechanism avoids the synchronization problem of multithreading and is loaded at the time of initialization. But it doesn't have the effect of lazy loading.
This is also the simplest implementation.

5) Static inner class

public class Singleton04 {

    // Static inner class
    private static class SingletonHolder {
        private static final Singleton04 INSTANCE = new Singleton04();
    }

    /**
     * Private construction method
     */
    private Singleton04(){}

    public static Singleton04 getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

In this way, when the Singleton04 class is loaded, its internal class will not be loaded, so the singleton class INSTANCE will not be initialized.
SingletonHolder is loaded only when getInstance method is explicitly called, thus instantiating INSTANCE.
Since the instance is created when the class is loaded, it is inherently thread safe. Therefore, it has the characteristics of lazy loading and thread safety.

6) Enumeration (claimed best)

public enum EnumSingleton01 {

    INSTANCE;

    public void doSomething() {
        System.out.println("doSomething");
    }
}

Simulation database link:

public enum EnumSingleton02 {

    INSTANCE;

    private DBConnection dbConnection = null;

    private EnumSingleton02() {
        dbConnection = new DBConnection();
    }

    public DBConnection getConnection() {
        return dbConnection;
    }
}

This method is advocated by Josh Bloch, author of Effective Java. It can not only avoid the problem of multi-threaded synchronization,
It also prevents deserialization and re creation of new objects.

2. Why is enumeration the best method?

The first five methods have the following three characteristics:

  • Construction method privatization
  • Instantiated variable reference privatization
  • Methods for obtaining instances are

First, privatizing the constructor is not safe. Because it cannot resist reflection attacks, the second is serialization and re creation of new objects. Let's verify.

1) Reflection verification

@Test
public void reflectTest() throws Exception {
    Singleton03 s = Singleton03.getInstance();

    // Get all constructors, including non-public ones
    Constructor<Singleton03> constructor = Singleton03.class.getDeclaredConstructor();
    constructor.setAccessible(true);

    // Construction example
    Singleton03 reflection = constructor.newInstance();

    System.out.println(s);
    System.out.println(reflection);
    System.out.println(s == reflection);
}

Output results:

org.yd.singleton.Singleton03@61e4705b
org.yd.singleton.Singleton03@50134894
false

Let's look at the test of enumeration classes

@Test
public void reflectEnumTest() throws Exception {
    EnumSingleton01 s = EnumSingleton01.INSTANCE;

    // Get all constructors, including non-public ones
    Constructor<EnumSingleton01> constructor = EnumSingleton01.class.getDeclaredConstructor();
    constructor.setAccessible(true);

    // Construction example
    EnumSingleton01 reflection = constructor.newInstance();

    System.out.println(s);
    System.out.println(reflection);
    System.out.println(s == reflection);
}

Output results:

java.lang.NoSuchMethodException: org.yd.singleton.EnumSingleton01.<init>()
    at java.lang.Class.getConstructor0(Class.java:3082)
    at java.lang.Class.getDeclaredConstructor(Class.java:2178)
    at org.yd.singleton.SingletonTest.reflectEnumTest(SingletonTest.java:61)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

Conclusion: through reflection, the private construction method of singleton pattern can also construct new objects. unsafe. The enumeration class throws exceptions directly, which shows that the enumeration class is safe for reflection.

2) Serialization validation

@Test
public void serializeTest(){
    Singleton03 s = Singleton03.getInstance();

    String serialize = JSON.toJSONString(s);
    Singleton03 deserialize =JSON.parseObject(serialize,Singleton03.class);

    System.out.println(s);
    System.out.println(deserialize);
    System.out.println(s == deserialize);

}

Output results:

org.yd.singleton.Singleton03@387c703b
org.yd.singleton.Singleton03@75412c2f
false

Conclusion: the two objects before and after serialization are not equal. So serialization is also unsafe.

Also look at the test of enumeration classes

@Test
public void serializeEnumTest(){
     EnumSingleton01 s = EnumSingleton01.INSTANCE;

     String serialize = JSON.toJSONString(s);
     EnumSingleton01 deserialize =JSON.parseObject(serialize,EnumSingleton01.class);

     System.out.println(s);
     System.out.println(deserialize);
     System.out.println(s == deserialize);
}

Output results:

INSTANCE
INSTANCE
true

Conclusion: the enumeration class serialization is safe.

To sum up, it can be concluded that enumeration is the best practice to implement singleton mode.

  • Reflection safety
  • Serialization / deserialization security
  • Simple writing

Keywords: Java Interview Programmer architecture Singleton pattern

Added by olanjouw on Sun, 12 Dec 2021 16:05:45 +0200