Singleton mode of design mode

1, Brief overview

  • Singleton mode is to take certain methods to ensure that there can only be one object instance for a class in the whole software system, and the class only provides a static method to obtain the instance.
  • The singleton pattern ensures that a class has only one instance, and will instantiate itself and provide this instance to the whole system to ensure its uniqueness.

Two. Pattern structure

It is usually implemented using a private constructor, a private static variable, and a public static function

3, Implementation mode

1. Hungry Chinese implementation

Thread safety, high calling efficiency, can not delay loading, and may cause a waste of resources

public class TestSingleton{
    public static void main(String[] args){
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        
        System.out.println(s1 == s2);
    }
}

// Realize one
class Singleton{
    private final static Singleton instance = new Singleton();
    
    private Singleton(){
        
    }
    
    public static Singleton getInstance(){
        return this.instance;
    }
}

// Implementation II
class Singleton{
    private final static Singleton instance;
    
    static{
        instance = new Singleton();
    }
    
    private Singleton(){
        
    }
    
    public static Singleton getInstance(){
        return this.instance;
    }
}

2. Lazy implementation

Single thread is safe, but in the case of multithreading, it is not safe, the calling efficiency is not high, the load can be delayed, and the resources can be saved

public class TestSingleton{
    public static void main(String[] args){
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        
        System.out.println(s1 == s2);
    }
}

// Thread unsafe
public class Singleton {
    private static Singleton instance;

    private Singleton() {
        
    }

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

// Thread safety
public class Singleton {
    private static Singleton instance;

    private Singleton() {
        
    }

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

3. Realization of double detection lock

Thread safety, high calling efficiency, delay loading and resource saving

public class TestSingleton{
    public static void main(String[] args){
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        
        System.out.println(s1 == s2);
    }
}

public class Singleton {

    private static volatile Singleton instance;

    private Singleton() {
        
    }

    public static Singleton getInstance() {
        // First judge whether instance has been instantiated. If not, lock the instantiation statement
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

[note]: instruction rearrangement may occur in the JVM, that is, in a multithreaded environment, an object may be allocated an address space but not initialized, so it is possible to create multiple objects. volatile keyword can solve the problem of instruction rearrangement and ensure that the variable value will be synchronized to main memory as soon as it changes.

4. Static internal class implementation

Thread safety, high calling efficiency, delay loading and resource saving

public class TestSingleton{
    public static void main(String[] args){
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        
        System.out.println(s1 == s2);
    }
}

public class Singleton {

    private Singleton() {
        
    }

    private static class InnerSingleton {
        private final static Singleton instance = new Singleton();
    }

    public static Singleton getInstance() {
        return InnerSingleton.instance;
    }
}

5. Enumeration class implementation

Thread safety, high calling efficiency, can not delay loading, and may cause a waste of resources

public class TestSingleton{
    public static void main(String[] args){
        Singleton s1 = Singleton.INSTANCE();
        Singleton s2 = Singleton.INSTANCE();
        
        System.out.println(s1 == s2);
    }
}

public enum Singleton {

   	INSTANCE;

    private String name;
    
    public String getName {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

[note]: this method is advocated by Effective Java author Josh Bloch. It can not only avoid the problem of multi-threaded synchronization, but also automatically support the serialization mechanism to prevent reflection or deserialization from damaging singleton.

4, Precautions

  • For the first four implementations mentioned above, we can actually destroy this singleton by means of reflection or deserialization.

    Destruction by reflection

    public class TestSingleton{
        public static void main(String[] args){
    		Constructor<?> constructor = Singleton.class.getDeclaredConstructor();
    		constructor.setAccessible(true);
            
            Singleton s1 = constructor.newInstance();
            Singleton s2 = constructor.newInstance();
            
            System.out.println(s1 == s2);
        }
    }
    
    class Singleton{
        private final static Singleton instance = new Singleton();
        
        private Singleton(){
            
        }
        
        public static Singleton getInstance(){
            return this.instance;
        }
    }
    

    Destruction by deserialization

    public class TestSingleton{
        public static void main(String[] args){
            Singleton s1 = Singleton.getInstance();
            
    		FileOutputStream fOutputStream = new FileOutputStream("Serialized file location");
    		ObjectOutputStream outputStream = new ObjectOutputStream(fOutputStream);
    		outputStream.writeObject(s1);
    		
            FileInputStream fInputStream = new FileInputStream("Serialized file location");
    		ObjectInputStream inputStream = new ObjectInputStream(fInputStream);
    		Singleton s2= (Singleton)inputStream.readObject();
    
            System.out.println(s1 == s2);
        }
    }
    
    class Singleton{
        private final static Singleton instance = new Singleton();
        
        private Singleton(){
            
        }
        
        public static Singleton getInstance(){
            return this.instance;
        }
    }
    
  • For the enumeration implementation, it is a singleton mode, and it will not be affected by reflection or deserialization due to the security guarantee provided by the JVM.

5, Advantages and benefits

  • Since the singleton pattern only generates one instance, the system performance overhead is reduced, especially when generating large objects
  • Since the singleton mode sets the global access point in the system, the shared resource access is optimized

6, How to choose

  • When the created object occupies less resources and does not pay attention to latency, hungry Han style or enumeration class is selected
  • When the created objects occupy more resources and pay attention to latency, lazy or static internal classes or double detection locks are selected

7, Application scenario

  • The application only needs one instance object of this class
  • Only one access point is allowed when a client calls an instance

For example, the Runtime class in the JDK source code

Keywords: Java Design Pattern

Added by Z33M@N on Thu, 07 Oct 2021 18:30:00 +0300