Detailed explanation of Java singleton mode

Brief description

A class has only one instance. It is created by itself and provides an entry for obtaining an instance. External classes can directly obtain the instance object through this entry.

Scene

In many cases, the whole application can only provide a global object. In order to ensure uniqueness, the reference of this global object cannot be changed again. For example, in an application, the configuration information of the server is stored in a file. These configuration data are uniformly read by a singleton class and instantiated into the only globally unique object, and then other objects in the application obtain these configuration information through this singleton object.
Such as objects in Spring container, Windows Task Manager, garbage collection bin, printer printing.

Realization idea
  1. Define the construction method of this class as a private method, so that other codes cannot instantiate the object of this class by calling the construction method of this class, and only get the unique instance of this class through the static method provided by this class.

class Person {
    
    private Person() {
        //This kind of external cannot be instantiated with the new keyword
    }
    
}

 2. Provide a static method in the class. When we call this method, we return the reference of the class instance.

    public static Person getInstance() {
        
        return new Person();
    }

Hungry Han style

/**
 * Hungry Han style single case writing
 */
public class Singleton1 {

    //Private construction
    private Singleton1(){}

    private static Singleton1 instance = new Singleton1();

    //Static factory method
    public static  Singleton1 getInstance(){
        return instance;
    }

}

Call and output log

 public class Main {

    public static void main(String[] args) {

        Singleton1 instance1 = Singleton1.getInstance();
        System.out.println(instance1);
        Singleton1 instance2 = Singleton1.getInstance();
        System.out.println(instance2);

    }
}

Printout:

com.t.Singleton1@4554617c
com.t.Singleton1@4554617c

After two transfers, the same object is output without re creation.

 

Starving singleton creates a static object for external use during class loading and initialization. This object will not change unless the system restarts, so it is thread safe in itself.

Disadvantages: Java reflection mechanism supports access to private attributes, so you can crack the construction method through reflection and generate multiple instances.

Lazy writing

/**
 * Lazy single example writing
 */
public class Singleton2 {
    
    //It's lazy. It doesn't load the object at first. It will be instantiated only when it is used
    private static Singleton2 instance = null;

    //Private construction
    private Singleton2(){}

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

}

Call and output log

    public static void main(String[] args) {

        Singleton2 instance1 = Singleton2.getInstance();
        System.out.println(instance1);
        Singleton2 instance2 = Singleton2.getInstance();
        System.out.println(instance2);

    }

Printout

com.t.Singleton2@4554617c
com.t.Singleton2@4554617c

Similarly, there are two transfers here. The same object is output without re creation.

Lazy type belongs to the category of delayed loading. The advantage is that it will be instantiated when it is used for the first time, but the disadvantage is that multiple single objects will be generated in a multithreaded environment. Now we use multithreading to crack lazy type singleton.

Multithreading cracking

//Multithreading cracking lazy singleton loading
class BreakThread extends Thread{
    @Override
    public void run() {
        Singleton2 instance = Singleton2.getInstance();
        System.out.println(instance);
    }
}

Call and output log

public class Main {

    public static void main(String[] args) {
        new BreakThread().start();
        new BreakThread().start();
    }

}

Printout

com.t.Singleton2@5169ce60
com.t.Singleton2@655a2155

Singleton2 object has been created twice, so why does this happen? Look at the code

    public static Singleton2 getInstance(){
        if (instance == null){
            //Threads execute asynchronously. The Singleton2 object has not been instantiated in the first thread,
            // instance is still null, and then the second thread executes
            instance = new Singleton2();
        }
        return instance;
    }

At this time, the lazy type will be vulnerable in multithreading mode, and multiple instances will be generated. How to solve it? Next, let's look at the writing method of double inspection lock

Double check lock writing method

Lazy multithreading improvement

/**
 * Double check lock
 * Improvement of lazy simple profit multithreading
 */
public class Singleton3 {
    
    private Singleton3(){}

    private static Singleton3 instance = null;

    public static Singleton3 getInstance(){
        //Equivalent to public synchronized static Singleton3 getInstance()
        if (instance == null){
            synchronized (Singleton3.class){
                if (instance == null){
                    instance = new Singleton3();
                }
            }
        }
        return instance;
    }

}

Call and output log

public class Main {

    public static void main(String[] args) {
        new BreakThread().start();
        new BreakThread().start();
    }

}

//Multithreading cracking lazy singleton loading
class BreakThread extends Thread{
    @Override
    public void run() {
        Singleton3 instance = Singleton3.getInstance();
        System.out.println(instance);
    }
}

Printout
com.t.Singleton3@5169ce60
com.t.Singleton3@5169ce60

After the double check lock is added, thread 2 will be executed only after thread 1 is executed, and only one instance will be created. This method is the single instance writing method commonly used in enterprises at present. A non null judgment is made before synchronized, which solves the problem of locking each access,

However, we can still access private construction methods through reflection, destroy instantiation rules and generate multiple instances, so enumeration came into being.

Enumeration writing method

/**
 * Single instance enumeration
 */
public enum Singleton4 {

    INSTANCE;

    public void something() {
        //Do what you want
    }

}

The above is the recommended writing method, but we are confused after reading it. We don't know what's going on and how to use it?

In fact, the enumeration method does not require you to obtain a singleton object, but directly do what you want to do through the enumeration class. A simple example is as follows

/**
 * Single instance enumeration
 */
public enum Singleton4 {

    INSTANCE;

    private Singleton4() {
        System.out.println("Enumeration construction method");
    }

    public String[] getProperties() {
        final String[] properties = new String[3];
        properties[0] = "Attribute 1";
        properties[1] = "Attribute 2";
        properties[2] = "Attribute 3";
        return properties;
    }

}

Call and output log

public class Main {

    public static void main(String[] args) {

        String[] properties1 = Singleton4.INSTANCE.getProperties();
        System.out.println(Arrays.asList(properties1));
        String[] properties2 = Singleton4.INSTANCE.getProperties();
        System.out.println(Arrays.asList(properties2));
    }

}

Printout

Enumeration construction method
[attribute 1, attribute 2, attribute 3]
[attribute 1, attribute 2, attribute 3]

It can be seen that the Singleton4 instance was executed only once.

Enumeration singleton writing method comes from the Book Effective Java

Description in the book: the enumeration type of single element enumeration has become the best method to realize Singleton

Think: why is enumerated singleton mode the best singleton mode?

1. The writing method is simple and clear

2. JDK defines the construction method of enumeration class as private. Each enumerated instance is of type static final, which means that it can only be instantiated once

3. Creating enumerations is thread safe by default, so you don't have to worry about threads

4. enum comes from enum class. Through JDK, we can see the construction of Emun class and the serialization mechanism provided by enumeration class

public abstract class Enum<E extends Enum<E>> implements Comparable<E>, 
      Serializable

It can prevent the default deserialization. The method declaration is as follows:

    /**
     * Block default deserialization
     * prevent default deserialization
     */
    private void readObject(ObjectInputStream in) throws IOException,
        ClassNotFoundException {
        throw new InvalidObjectException("can't deserialize enum");
    }

    private void readObjectNoData() throws ObjectStreamException {
        throw new InvalidObjectException("can't deserialize enum");
    }

5. In newInstance in reflection, instantiation of enumeration is prohibited. See line 416 of Constructor class for details

@CallerSensitive
    public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException{
        //... ellipsis
       
        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
           
        //... ellipsis

}
   

Finally, no matter what plan you take, please always keep in mind the three main points of the single example:

  • Thread safety
  • Delayed loading
  • Serialization and deserialization security

 

 

Keywords: Java Design Pattern

Added by dta on Wed, 09 Feb 2022 13:57:55 +0200