Design mode - singleton mode

Posted by DoctorWho on Wed, 02 Feb 2022 23:42:45 +0100

2. Singleton mode

Singleton design pattern is one of the most commonly used design patterns in software development. That is, a class can only have one instance object in the whole system, which can be obtained and used. For example, the Runtime class representing the JVM Runtime environment.

1) Attention

  • How to ensure that there is only one instance

    Constructor privatization

  • You must create this instance yourself

    A static variable containing this class holds this unique instance

  • Provide this example to the whole system by yourself

    Provide external access to the instance object

    • Direct leakage; Get with get method of static variable

2) Common forms of implementation

1. Hungry Han style

Creating objects directly does not have thread safety problems

Hungry: in a hurry, whether you use it or not, create it first

1.1 direct instantiation of hungry Chinese style, concise and intuitive
  • Note that the constructor is privatized, self created and saved with static variables, and provides instances to the outside world
package interview.singleton;
//Hungry Chinese style, directly create an instance object, and the object will be created whether you need it or not
public class Singleton1 {
    public static final Singleton1 INSTANCE=new Singleton1();
    private Singleton1(){
        
    }
}

Get this object

package interview.singleton;

public class TestDemp {
    public static void main(String[] args) {
        Singleton1 s=Singleton1.INSTANCE;
        System.out.println(s);
    }
}

1.2 enumeration is the most concise
package interview.singleton;
//Hungry man, enumeration
/*
* Enumeration type: indicates that there are a limited number of objects of this type. If it is limited to one, it becomes a single instance
* */
public  enum Singleton2 {
            INSTANCE
}

Get object

package interview.singleton;

public class TestDemp {
    public static void main(String[] args) {
        Singleton2 s=Singleton2.INSTANCE;
        System.out.println(s);
    }
}

However, the result printed in this way is different from the first one. Print the object name INSTANCE directly

1.3 the form of static code block is suitable for complex instantiation

You may need to read a bunch of initialization data from the configuration file

package interview.singleton;

import java.io.IOException;
import java.util.Properties;

public class Singleton3 {
    //Create it yourself and save it with static variables
    public  static  final Singleton3  INSTANCE;
    private  String info;

    static {

        try {
            Properties properties=new Properties();
            //Because the configuration file is placed under src, it can be loaded using class loader
            properties.load(Singleton3.class.getClassLoader().getResourceAsStream("single.properties"));
            INSTANCE= new Singleton3(properties.getProperty("info"));
        } catch (IOException e) {
           throw new RuntimeException(e);
        }
    }
    //Constructor privatization
    private  Singleton3(String info){
        this.info= info;
    }
}

The corresponding configuration file/ src/single.propertites

info=sosleepy

2. Lazy style

2.1 thread unsafe, applicable to single thread
package interview.singleton;

/*
* Constructor privatization
* Save this unique instance with a static variable
* Provide a static method to get the instance object
*
* Lazy: delay the creation of this instance object,
* */
public class Singleton4 {
    private  static  Singleton4 instance;
    private  Singleton4(){

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

test

package interview.singleton;

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

Thread safety issues involved:

package interview.singleton;

import java.util.concurrent.*;

public class TestDemp {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
//        Singleton4 s1=Singleton4.getInstance();
//        Singleton4 s2=Singleton4.getInstance();
//        System.out.println(s1==s2);
        Callable<Singleton4> c=new Callable<Singleton4>() {
            @Override
            public Singleton4 call() throws Exception {
                return Singleton4.getInstance();
            }
        };
        //Create thread pool
        ExecutorService es= Executors.newFixedThreadPool(2);
        Future<Singleton4> f1=es.submit(c);
        Future<Singleton4> f2=es.submit(c);

        Singleton4 s1=f1.get();
        Singleton4 s2=f2.get();
        System.out.println(s1==s2);
        es.shutdown();

    }
}

The output is false

2.2 thread safety (applicable to multithreading)

Solve with synchronization

package interview.singleton;

public class Singleton5 {
    private  static  Singleton5 instance;
    private  Singleton5(){

    }
    public static  Singleton5 getInstance(){
        synchronized (Singleton5.class){  //The current class is a lock object
            if(instance==null){
                instance=new Singleton5();
            }
            return instance;
        }
        }

}

package interview.singleton;

import java.util.concurrent.*;

public class TestDemp {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
//        Singleton4 s1=Singleton4.getInstance();
//        Singleton4 s2=Singleton4.getInstance();
//        System.out.println(s1==s2);
        Callable<Singleton5> c=new Callable<Singleton5>() {
            @Override
            public Singleton5 call() throws Exception {
                return Singleton5.getInstance();
            }
        };
        //Create thread pool
        ExecutorService es= Executors.newFixedThreadPool(2);
        Future<Singleton5> f1=es.submit(c);
        Future<Singleton5> f2=es.submit(c);

        Singleton5 s1=f1.get();
        Singleton5 s2=f2.get();
        System.out.println(s1==s2);
        es.shutdown();

    }
}

Output results

true

Process finished with exit code 0

For multithreading, there is no need to lock the subsequent threads. Add an if judgment to improve the performance

package interview.singleton;

public class Singleton5 {
    private  static  Singleton5 instance;
    private  Singleton5(){

    }
    public static  Singleton5 getInstance(){
        if(instance==null){
            synchronized (Singleton5.class){  //The current class is a lock object
                if(instance==null){
                    instance=new Singleton5();
                }

            }

        }
        return instance;
        }

}

2.3 static internal class form, applicable to multithreading

package interview.singleton;

/*
*The object is created only when the internal class is loaded and initialized
*Static internal classes will not be automatically initialized with the loading and initialization of external classes. They need to be loaded and initialized separately.
* Because it is created when the internal class is loaded and initialized, it is thread safe.
* */
public class SIngleton6 {

    private  SIngleton6(){

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

Summary: hungry Han style, enumeration form is the simplest; Lazy, static internal class form is the simplest.

Topics: Java Design Pattern