The singleton pattern


This time, I will briefly talk about the single example model. In the process of speaking, the author will try to make this model clear, detailed, simple. Examples of current use of the singleton pattern will also be presented, and the implementation of these cases will be analyzed. In this process, you will find that there is a great deal of knowledge in the small singleton pattern.

The singleton pattern is designed to ensure that there is only one object per class in a JVM environment. In general, when we talk about singletons, we think of implementations (lazy, hungry) that are enforced by the design of the code. They can be called mandatory singletons. Of course, through documentation, through coding constraints, you can also think of achieving the effect of only one object per class.

In general, the singleton pattern is required for classes that have connection capabilities (such as database connections, network connections, printer connections), configuration capabilities, tools, and auxiliary systems in a project. Most of these classes consume significant system resources to create and destroy, or do not require the creation of multiple objects (objects are indistinguishable from one another).

In Java, there are two essential steps to creating a singleton pattern

  1. Privatize all constructors of a class to prevent other code from creating objects outside the class,
  2. Provides a static method to get the object to return a unique object reference for the class.

Based on the above two, three singleton patterns can be written simply:

The first way is to hold a reference to an object of the class through a static variable of the class, with the final keyword preventing it from being reassigned.

public class Singleton1 {
    private static final Singleton1 INSTANCE = new Singleton1();
 private Singleton1(a){}
    public static Singleton1 getInstance(a){
        returnINSTANCE; }}Copy the code

Second method: This method is similar to the first method, using static variables to maintain references to the class, but putting object creation in static code blocks.

public class Singleton2 {
    private static final Singleton2 INSTANCE ;
    static {
        INSTANCE=new Singleton2();
    }
    private Singleton2(a){}
    public static Singleton2 getInstance(a){
        returnINSTANCE; }}Copy the code

Third, use static variables to maintain a reference to an object of the class (in which case the final keyword cannot be used due to the limitations of Java syntax), and determine and create the object in the method of obtaining the object.

public class Singleton3 {
    private static Singleton3 instance;
 private Singleton3(a){}

    public static Singleton3 getInstance(a) {
        if(null==instance){
            instance=new Singleton3();
  }
        returninstance; }}Copy the code

In the first two cases, object creation is placed in the initialization phase of the class, and in the latter case, object creation is placed in the use phase of the class. The first two are known as the hungrier style and the third as the slacker style. The advantage of hangry is that it is easy to understand, but the disadvantage is that it does not achieve lazy loading effect. If the instance is never used, connection resources and memory are wasted.

But lazy style is not complicated, can play a lazy loading effect. Therefore, readers may prefer to use lazy, or variations of it (such as lazy with double-checked locks). You say it’s memory saving, lazy to load, and cool.

But what about the facts? To make sense of these two singletons, it is necessary to briefly recall the life cycle of the class.

  1. Class loading: The process of loading a class’s bytecode files (.class files) from the hard disk into the method area
  2. Class wiring: This process consists of three parts: validation, preparation, and parsing,
  3. Class initialization: Assign values to static variables in the following order: parent static variables -> static code block -> subclass static variables -> subclass static code block. Hanhanian object creation is in this stage
  4. The use of classes, such as class instantiation, lazy object creation is in this phase, and the new keyword can trigger the life cycle
  5. Class unloading

So the question is, when is the class initialized? Based on the five life cycle phases of the class, we only need to verify that those actions prior to the creation of the object trigger the initialization of the class. I use JDK1.8, default configuration, a simple experiment. First add a print statement to the constructor, print “init”, and then add a static method and a static variable. Test Singleton1.

public class Singleton1 {
  private static final Singleton1 INSTANCE = new Singleton1();
  // Add a print statement
  private Singleton1(a){
        System.out.println("init");
  }
  public static Singleton1 getInstance(a){
        return INSTANCE;
  }
    // Static method
  public static final void otherMethod(a){}// Static variables
  public static final int staticFiled=0; 
 }

Copy the code

Test 1: Just declare

/ / test 1:
public class Test {
    public static void main(String[] args) {
        System.out.println("-------start-------");
        Singleton1 singleton1 = null;
        if(null==singleton1){
            System.out.println("singleton1 is null");
  }
        System.out.println("-------end-------");
  }

    /* out: * -------start------- * singleton1 is null * -------end--------- */ 
}
Copy the code

From the output, the declaration alone does not trigger the initialization phase of the class. Test 2: Call a static variable of the class

/ / test 2:
public class Test {
    public static void main(String[] args) {
      System.out.println("-------start-------");
      System.out.println(Singleton1.staticFiled);
      System.out.println("-------end---------");
  }

    /* out: *-------start------- *0 *-------end--------- */ }
Copy the code

From the output, simply calling a static variable of the class does not trigger the initialization phase of the class. Test 3: Calling a static method of a class

/ / test 3
public class Test {
    public static void main(String[] args) {
        System.out.println("-------start-------");
        Singleton1.otherMethod();
        System.out.println("-------end-------");
  }

    /* out: *-------start------- * init *-------end------- */ 
}
Copy the code

From the output, simply calling the class’s static methods triggers the class’s initialization phase.

From the above three examples, it can be seen that hungry, in some cases, can also show lazy loading effect, and hungry is simple, and does not cause thread safety problems, in some cases can replace lazy. And with the development of hardware today, the memory saving benefits of slacker style can be slowly ignored.

In design, slacker style is better than hungry, in use, can just solve the problem is good design.

In the case of multiple threads, the slacker style can be modified somewhat. When two threads block in an if(null==instance) statement, it is possible for two threads to enter the creation instance and return two objects. This is a probabilistic problem, and once it occurs, it is a matter of luck to detect and locate the problem. To do this, we can lock to ensure that only one thread is in the getInstance() method at a time, thus ensuring thread consistency. The singleton mode in multithreading can be

public class Singleton4 {
    private static Singleton4 instance;
 private Singleton4(a){}

    public static synchronized Singleton4  getInstance(a) {
        if(null==instance){
            instance=new Singleton4();
  }
        returninstance; }}Copy the code

Singleton4, as opposed to Singleton3, simply adds a lock on getInstance (static methods use Singleton4.class as the lock, non-static methods use this as the lock). This ensures that only one thread at a time enters the internal code fast. For example, if there are 100 instances in a project, the JVM will lock and release the lock 100 times, but only once the object is created. The JVM lock and release the lock all need to read and write the object header, and each operation is expensive. Therefore, the efficiency of the singleton mode implemented in this way is not high. The probability of instance not being null is very, very high, but it is compatible with the security of multiple threads at the same time, so another layer of judgment can be added outside. It can be written as follows

public class Singleton4 {
    private static Singleton4 instance;
    private Singleton4(a){}

    private static synchronized void doGetInstance(a) {
        if(null==instance){
            instance=newSingleton4(); }}public static synchronized Singleton4 getInstance(a){
        if(null==instance){
            doGetInstance();
        }
        return instance;
  }
Copy the code

To simplify the code, it can be written as follows:

public class Singleton5 {

    private static Singleton5 instance;  
    private Singleton5(a) {}public static Singleton5 getInstance(a) {
        if (null == instance) {
            synchronized (Singleton5.class) {
                if (null == instance) {
                    instance = newSingleton5(); }}}returninstance; }}Copy the code

In this form, known as the double-checked singleton, if multiple threads pass the first check at the same time, and one thread passes the second check first and instantiates the object, the remaining threads that pass the first check do not instantiate the object. It’s more efficient, but it can look awesome.

But there are some problems with the above form. Remember the life cycle of a class? Here we expand on the class connection process in more detail.

The join process of a class can be divided into validation phase, preparation phase and parsing phase. Needless to say, the validation phase is where the JVM validates the validity of a class (many JVA-BASED languages have their own tools to generate Java bytecode, such as Clojure, Kotlin, etc.).

The preparation phase assigns default values to static variables in the class, and the parsing phase converts symbolic references to direct references. Also, if a class is referenced directly, initialization of the class is triggered (next in the join process). In general, a class encounters the new keyword in the following order:

  1. Open up space,
  2. The symbol references the space and initializes the class in the space,
  3. Converts a match reference to a direct reference (when if(null==instance) returns false).

In practice, however, to reduce CPU idle time, JVMS often reorder instructions to form an instruction pipeline. That is to say, three deployments may be out of order and may be

  1. Open up space,
  2. To a direct reference (if(null=instance) returns false),
  3. Initialize the class.

The above double-checking mechanism is therefore problematic: it is possible to return a class that is not fully initialized.

The effect of volatile

  1. Visibility: Thread and heap memory can be compared to the core and main memory of a computer’s CPU. Each core of the CPU has its own cache, and frequently used data is written to its cache first, and then to main memory. This can result in the latest data not being stored in main memory in a timely manner, but it can greatly improve efficiency. In the same JVM, each thread has its own memory area. Using volatile variables (not temporary variables in a method, which reside on the JVM stack) forces every read and write to be written to the heap, achieving the effect of up-to-date data that can be shared by all threads.
  2. Disallow instruction reordering optimization: As discussed above, volatile variables insert a memory barrier (don’t be alarmed by this term) at the end of assignment to prevent instruction reordering. Volatile improves data consistency but slows down the rate. The above notation needs to be modified slightly:
public class Singleton5 {

    private static volatile Singleton5 instance;         
    private Singleton5(a) {}
    public static Singleton5 getInstance(a) {
        if (null == instance) {
            synchronized (Singleton5.class) {
                if (null == instance) {
                    instance = newSingleton5(); }}}returninstance; }}Copy the code

At this point, we can talk about Singleton1(hungry) writing, as mentioned above, the problem with this writing is that it does not achieve lazy loading effect. But it has a clear structure, thread-friendly characteristics. If you can simply modify its structure to make it lazy loading, that would be perfect!

The use of static nested classes

  • Statically nested class: a nested class declared outside of a class that, because it is static, can be called directly by the class name without initialization.
  • Inner class: That is, this class is a member of another class and therefore can only be created by referring to another class. By statically nesting classes, you can achieve the lazy effect of hungering.
public class Singleton7 {
    private Singleton7(a){}
    private static  class SingletonHolder {
        private static  Singleton7 INSTANCE = new Singleton7();
  }
    public static final Singleton7 getInstance(a) {
        returnSingletonHolder.INSTANCE; }}Copy the code

What’s changed here compared to Singleton1? An object of only one class is wrapped in a statically nested class. To see if lazy loading is implemented in this way, examine the statement new Singleton7(); When it was called. When compiling Singleton7 using Javac, three class files are generated:

  1. Singleton7$1.class
  2. Singleton7$SingletonHolder.class
  3. Singleton7.class

The first file can be ignored and is an empty shell file (the reader can decompile the plug-in to see the source code). You can see that a statically nested class exists as a class on its own, where the logic to create the object resides in the nested class, and the JVM cannot create the object until it reads the bytecode of the nested class. Reading class files from hard disk and then allocating space in memory is a laborious task, so the JVM chooses to load on demand, not load what is not necessary, and not allocate what is not necessary.

So Singleton7 does not read statically nested class files from connection and initialization, and of course cannot create Singleton7 objects. When getInstance is called, the JVM has to load the bytecode file, but it doesn’t necessarily need to initialize the class. So the conclusion is: wrapping the creation of an object with a static nested class allows lazy loading without static nested classes initializing it! Let’s start with the experimental verification. First modify Singleton7 and add log:

public class Singleton7 {
    private Singleton7(a){
        System.out.println("Singleton7");
  }
    private static final class SingletonHolder {
        SingletonHolder(){
            System.out.println("SingletonHolder");
  }
        private static final Singleton7 INSTANCE = new Singleton7();
  }
    public static Singleton7 getInstance(a) {
        returnSingletonHolder.INSTANCE; }}Copy the code

The test class:

public class Test {
    public static void main(String[] args) {
        System.out.println("-------start-------");
        Singleton7.getInstance();
        System.out.println("-------end---------");
  }
    /* out: *--------start------ *Singleton7 *-------end--------- */ 
}
Copy the code

No output SingletonHolder!! What does this show? This seems to be the end of the story: we seem to have strictly and perfectly implemented a class with only one object in a JVM!! But is that really the case?

Break the singleton pattern with JAVA reflection

In the singleton above, the most important step is to privatize the constructor so that the outside world cannot new the object. But Java reflection can enforce access to private modified variables, methods, and constructors! So:

Public class Test {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class<Singleton7> singleton7Class = Singleton7.class;
  Constructor<Singleton7> constructor= singleton7Class.getDeclaredConstructor();
  // There is a wicked way
  constructor.setAccessible(true);
  Singleton7 singleton1=constructor.newInstance();
  Singleton7 singleton2=constructor.newInstance();
  }
    /**out * Singleton7 * Singleton7 */ 
}
Copy the code

It seems our singleton is not safe after all. In Java, there are four ways to create objects

way instructions
new The constructor needs to be called
reflection The constructor needs to be called to be immune to any access restrictions (public,private, etc.)
clone The Cloneable interface needs to be implemented, and can be divided into deep replication and shallow replication
serialization 1. Store objects on hard disks. 2

The singleton patterns described above are not resistant to reflection, Clone, and serialization.

Now consider how to secure the singleton pattern. For Clone and serialization, Cloneable and Serializable interfaces can be implemented without directly or indirectly during the design process. For reflection, it is generally difficult to avoid using ordinary classes (you can avoid this by calling the second constructor, but this is not a complete solution; you can search for details).

Enumeration class

Enumerated classes, part of a new feature in Java 5, are a special kind of data type that is special because it is a class type but has some special constraints over class types. Enumeration classes can implement interfaces, but they cannot inherit from classes. Enumeration classes defined with enum inherit from java.lang. enum classes by default at compile time, instead of inheriting from ordinary Object classes. Enumeration classes implement Serializable and Comparable interfaces by default, and are not inherited because the compiler adds a final class to the class with the enum declaration. Enumeration values defined internally within an enumeration class are instances of that class. Other than that, enumerated classes are the same as ordinary classes. So you can use enumerated classes to implement a singleton pattern

public enum Singleton8
{
    INSTANCE;
  // This method is optional
  public static Singleton8 getInstance(a){
        return INSTANCE;
  }
    //.....other method 
}
Copy the code

How does this prevent reflection from destroying classes? Take a look at the code snippet below

public T newInstance(Object ... initargs)
    throws InstantiationException, IllegalAccessException,
  IllegalArgumentException, InvocationTargetException
{
    if(! override) {if(! Reflection.quickCheckMemberAccess(clazz, modifiers)) { Class<? > caller = Reflection.getCallerClass(); checkAccess(caller, clazz,null, modifiers); }}Reflection checks whether the class is an ENUM modifier when creating objects through newInstance. If so, an exception is thrown, and reflection fails.
    if((clazz.getModifiers() & Modifier.ENUM) ! =0)
        throw new IllegalArgumentException("Cannot reflectively create enum objects");
  ConstructorAccessor ca = constructorAccessor; // read volatile
  if (ca == null) {
        ca = acquireConstructorAccessor();
  }
    @SuppressWarnings("unchecked")
    T inst = (T) ca.newInstance(initargs);
 return inst; }
Copy the code

The interception is Java. Lang. Reflect. NewInstance Constructor class, you can see, when the current class is enumerated types, it throws an exception, thus enumeration class can fight to reflection attack!

Since the enumeration class implements Serializable by default, you can serialize the enumeration class

public class Test2 {
    public static void main(String[] args) throws Exception{
        File objectFile =new File("Singleton8.javaObject");
        Singleton8 instance1=Singleton8.INSTANCE;
        Singleton8 instance2=null;    
        // Serialize to local
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(objectFile));
        objectOutputStream.writeObject(instance1);
        objectOutputStream.flush();    
        // Deserialize to memory
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(objectFile));
        instance2= (Singleton8) objectInputStream.readObject();           objectInputStream.close();
        objectOutputStream.close();
        //true, indicating that both references the same objectSystem.out.println(Objects.equals(instance1,instance2)); }}Copy the code

Enumeration classes inherit java.lang.Enum classes directly, instead of Object classes, and cannot copy operations.

This is the end of the singleton design approach, and I’ll give you two simple examples.

1. Implement singleton in thread

The intuitive way to implement in-thread singletons is to use a map<Long,Singleton9> to store objects. Key can be the ID of a thread, and value is an object unique to each thread. We can do better than that by using ThreadLocal for thread variable isolation! Thread-level singletons are designed as follows

public class Singleton9 {
    private Singleton9(a){}
    private static final ThreadLocal<Singleton9> threadHolder = new ThreadLocal<>(){
        @Override
  protected Singleton9 initialValue(a) {
            return newSingleton9(); }};public static final Singleton9 getInstance(a){
        returnthreadHolder.get(); }}Copy the code

2. Singleton multithreaded mode of HttpServlet

Tomcat servlets are created and loaded as needed, and subsequent requests will use the same Servlet object. Is a typical singleton multithreaded pattern, as seen in the loadServlet in StandardWrapper in Tomcat. For singleton multithreading, note the following

  1. Use stack containment to manage variables, enclosing variables in the thread stack to prevent them from being contaminated by other threads.
  2. Use ThreadLocal for variable isolation of instance variables
  3. Implementing the SingleThreadModel interface: Service methods in servlets implementing SingleThreadModel will not have two threads executing at the same time.

The design mode of the program is more practical than theoretical. Therefore, a design pattern is a good design pattern as long as it solves the problem exactly.

The above content is just personal collation, please read objectively, for the wrong place, also please readers can point out and correct in time, welcome to discuss together!

email:[email protected]