I study design patterns – singleton patterns

As we all know, many great frameworks rely on so-called design patterns, which not only make our code more readable, robust, and extensible, but also make it more successful… Pull the

Of course, learning design patterns can improve and expand our code design ideas, enrich our technical stack, and effectively solve the problems that may occur in the work, isn’t it beautiful.

Now, let’s do singletons, okay? I still vaguely remember one of the design patterns I often recite in autumn – singleton pattern, of course I have more than one, ha ha ha.

But now that I’m working, I want to look again at patterns like singletons that often appear in good framework source code.

background

What is the singleton pattern? There is only one instance object in the whole process of running the program. It is as simple as that.

But when do we use it? What problem arises that causes us to use the singleton pattern? Is this an essential part of learning a skill or knowledge? The power?

It has its advantages:

Having only one instance object in memory saves memory space, avoids repeated creation and destruction of objects, improves performance, avoids repeated occupation of multiple resources, and enables global access.

What kind of scenes would you use?

Objects that need frequent instantiation and destruction, stateful utility objects, and frequent access to database or file objects, such as printers, database connection pools, log management, and application configuration.

But what’s missing? I might want to talk about it a little bit more

In object-oriented languages, we all know that static and non-static methods are stored in the Method Table in memory. When a class is first loaded, it writes both static and non-static methods to the Method Table in the Loader Heap. And the Loader Heap is not controlled by the GC, so once loaded, the GC will not reclaim it until the AppDomain is unloaded.

And non-static methods when creating an instance object, because the value of the attribute for each object is different, so in the new one instance, will make a copy of the instance attributes in the GC Heap, and the new object on the stack, the stack pointer points to the copy just now that the memory address of an instance.

Static methods do not need static fields, because the static fields in static methods are stored in the Method Table.

So the static method and the non-static method, in terms of call speed, the static method is obviously faster, because the non-static method has to be instantiated, allocate memory, and the static method doesn’t, but the difference in call speed is actually negligible.

We might as well think, if we all use static method, no non-static method, can not achieve the same function? Yes, that’s right, but your code is object based, not object oriented, because object oriented inheritance and polymorphism are non-static methods. (To put it bluntly, a pattern difference, or pattern difference)

Moreover, all use a static method is not recommended, suppose if multi-threaded cases, if a static method using a static field, the static fields can be modified by multiple threads, so if in a static method using the static variables, it may appear thread safety problem, of course, if not multi-threading, because only a static field, There is also the problem of being modified elsewhere.

Getting to the point, why use singletons instead of static methods?

A method should be static if it has nothing to do with an instance of its class, and non-static if it has nothing to do with an instance of its class. If we really should use non-static methods, but only need to maintain a single instance when creating a class, we need to use the singleton pattern.

For example, such as system load some configuration information and attributes, these configurations and properties are must exist, and the public, at the same time need to exist in the whole life cycle, so just need a, this time if you need when I need again the new one, to assign values to him again, obviously is a waste of memory and assignment doesn’t make sense, That’s why singletons are so important.

Implementation of Java singleton

There are two ways to build the Java language

  1. Hungry man, you can understand that this guy is very hungry, (first cooked bread, put it on the plate, when I want, I directly from the plate, eat)
  2. Lazy, you can understand lazy loading, (the materials are ready, when you are hungry, I will start to make bread, ready, here you are, this buddy is very stable)

There are some common points to note about both approaches:

You can think of it as steps

  1. The constructor, which has to be private, is to prevent you from going crazy with new objects, so IF I close a dark room for you, you can’t go crazy with new objects
  2. This instance variable is globally static
  3. This method is globally static

The hungry

According to the above conditions, directly open round:

public class Singleton {
    // First static instance variables
    private static Singleton uniqueInstance = new Singleton();

    // Second, private constructor
    private Singleton(a){}

    // Finally static method, return static instance variable
    public static Singleton getInstance(a) {
        returnuniqueInstance; }}Copy the code

Isn’t it easy to feel hungry…

So? The JVM creates a single instance of this class as soon as it is loaded, whether it is used or not. If it is never used, space is wasted. Typical space swap time, no judgment is required each time the class is called, saving runtime.

Slob (Flawed version)

The problem that this elder brother appears most, also be an interview often take an examination of, hated most…

Start with the original slob, step by step, throwing out his problems…

public class Singleton {
    // Three steps
    private static Singleton uniqueInstance;

    private Singleon(a){}

    public static Singleton getInstance(a) {
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton();
        }
        returnuniqueInstance; }}Copy the code

Let’s break it down:

Suppose you have two threads A and B calling the static method of getInstance

UniqueInstance = new Singleton(); When B decided that uniqueInstance == NULL and then walked to the side of A, he suddenly fell into A big pit, which was so embarrassing… When A returns uniqueInstance, the instance variable points to an address 0x1(empty valley).

When B is saved by a good man, he finds that he still has the task given by the top. He quickly creates a new one and runs quickly. At this time, this instance variable points to an address of 0x2, which is not the original simple and lovely 0x1.

One easy solution, however, is to prefix the getInstance() method with the synchronized keyword, as follows

Slacker (ok version)

public static synchronized Singleton getInstance(a) {
    if (instance == null) {
        uniqueInstance = new Singleton();
    }
    return uniqueInstance;
}
Copy the code

Is there anyone out there who doesn’t know what synchronized is? Synchronized: You go to the bathroom, open the door, lock it, and no one can get in.

The code above, directly on the entry method of a lock, first come, first served, wait for the buddy finished, you can go…

But is it not elegant? Or worse performance, after all synchronized keyword heavy weight lock, does not know?

Although after JavaSE1.6 synchronized keyword mainly includes: biased lock and lightweight lock introduced in order to reduce the performance consumption caused by lock acquisition and lock release and other various optimizations, the execution efficiency has been significantly improved.

However, every time getInstance() is used in a program, it passes through the layer of synchronized locking, which inevitably increases the time consumption of the getInstance() method and may block.

Lazybones (double check, interview is also often a version of the test…)

The meaning of double check, you can temporarily understand as double if

public class Singleton {
    //1. mark
    private volatile static Singleton uniqueInstance;
    private Singleton(a) {}public static Singleton getInstance(a) {
       //2. mark: Check the instance, if not, enter the synchronized code block
        if (uniqueInstance == null) {
            //3. Mark: Only the first time I fully implemented the code here
            synchronized(Singleton.class) {
               //4. mark: Enter the synchronization block, check again, if it is still null, then create the instance
                if (uniqueInstance == null) {
                    uniqueInstance = newSingleton(); }}}returnuniqueInstance; }}Copy the code

Mark, there are four codes in the above code, please explain them respectively, you should make these four points clear when you interview…

  1. mark: volatile

The characteristics of volatile are well known: memory visibility, and instruction reordering prohibition

First, the memory visible, this and JMM memory architecture has a little relationship, all in pursuit of fast, the pursuit of CPU utilization, rather than crazy waiting… Therefore, one working memory is always constructed, but how to ensure that the variables in multiple working memory are visible is to take advantage of the MESI cache consistency protocol of the total cache to maintain… Volatile can erase the current value of a variable from someone else’s working memory, sending them back to the general cache.

Disarrange the order of instructions when the single thread does not affect the result, which can improve speed and efficiency. However, in the case of multi-thread, the result may be inconsistent. In order to ensure consistency, disallow the reorder. The bottom is divided into three steps:

memory =allocate();    //1. Allocate the memory space of the object
ctorInstance(memory);  //2. Initialize the object
instance = memory;     //3. Set instance to the newly allocated memory address
Copy the code

In the above three instructions, Step 2 depends on Step 1, but Step 3 does not depend on Step 2. Therefore, THE JVM may conduct instruction rebeat optimization for them, and the rearranged instructions are as follows:

memory =allocate();    //1. Allocate the memory space of the object
instance = memory;     //3. Set instance to the newly allocated memory address
ctorInstance(memory);  //2. Initialize the object
Copy the code

After this optimization, the memory initialization is placed after the instance allocation, so that when thread 1 executes the assignment in step 3, another thread 2 enters the getInstance method to check that instance is not null. The instance that thread 2 gets is not initialized yet, so using it will cause an error.

  1. Mark: The first if

Synchronized is cumbersome, lock code blocks, multithreading can not come into the block every time, is it not blocked, such as the class lock, directly to his top judge not empty directly jump out. Improved performance wow.

  1. Mark: Synchronized

  2. Mark: The second if

Assuming there is no second if, if A is in A new object and B is there waiting for you to release the lock, after A releases the lock, B will come in and new again. If there is A second if, it will turn its head and walk away, and it will not be new again…

Lazy (Static inner class)

public class Singleton {  
    private static class SingletonHolder {  
        private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (a){}  
    public static final Singleton getInstance(a) {  
    returnSingletonHolder.INSTANCE; }}Copy the code

The SingletonHolder class is explicitly loaded to instantiate instance only when the getInstance method is explicitly called (it is loaded only when an instance of this singleton is used for the first time, and there are no thread-safety issues).

Hungry (enumeration)

This implementation is not yet widely adopted, but it is the best way to implement the singleton pattern. It is more compact, automatically supports serialization, and absolutely prevents multiple instantiations (if the singleton class implements the Serializable interface, by default a new instance object is always created for each deserialization.

public enum Singleton {
	 // Define an enumerated element that is an instance of Singleton
    INSTANCE;  
    
    public void doSomeThing(a) {  
	     System.out.println("Enumeration method implementation singleton"); }}Copy the code

How to use:

public class ESTest {

	public static void main(String[] args) {
		Singleton singleton = Singleton.INSTANCE;
		singleton.doSomeThing();//output: the enumeration method implements singletons}}Copy the code

Golang

Too many reasons, not in detail, you can see the evolution step by step

Defective version

type singleton struct {}

var instance *singleton

func GetInstance(a) *singleton {
	if instance == nil {
		instance = &singleton{}   // It is not concurrency safe
	}
	return instance
}
Copy the code

Put a lock on it. Let’s figure it out

var mu Sync.Mutex
var instance *singleton

func GetInstance(a) *singleton {
    mu.Lock()                    // There is no need to lock if the instance exists
    defer mu.Unlock()

    if instance == nil {
        instance = &singleton{}
    }
    return instance
}
Copy the code

A little heavy, the granularity of the lock is a little coarse, so fine, want to fine, will be double check

var mu Sync.Mutex
var instance *singleton
func GetInstance(a) *singleton {
    if instance == nil {     // It's not perfect because it's not completely atomic
        mu.Lock()
        defer mu.Unlock()

        if instance == nil {
            instance = &singleton{}
        }
    }
    return instance
}
Copy the code

Then make it perfect

import "sync"
import "sync/atomic"

var initialized uint32
var instance *singleton
... // omit here

func GetInstance(a) *singleton {

    if atomic.LoadUInt32(&initialized) == 1 {  // Atomic operation
		    return instance
	  }

    mu.Lock()
    defer mu.Unlock()

    if initialized == 0 {
         instance = &singleton{}
         atomic.StoreUint32(&initialized, 1)}return instance
}
Copy the code

But the code is a little bit complicated. Can we simplify it? There is a simple Do method using sync.once

package singleton

import (
    "sync"
)

type singleton struct {}

var instance *singleton
var once sync.Once

func GetInstance(a) *singleton {
    once.Do(func(a) {
        instance = &singleton{}
    })
    return instance
}
Copy the code

Note: If the first Do fails, you can design a retry mechanism to save it…

Add Once source code

// Once is an object that will perform exactly one action.
type Once struct {
	// done indicates whether the action has been performed.
	// It is first in the struct because it is used in the hot path.
	// The hot path is inlined at every call site.
	// Placing done first allows more compact instructions on some architectures (amd64/x86),
	// and fewer instructions (to calculate offset) on other architectures.
	done uint32
	m    Mutex
}

func (o *Once) Do(f func(a)) {
	if atomic.LoadUint32(&o.done) == 0 { // check
		// Outlined slow-path to allow inlining of the fast-path.
		o.doSlow(f)
	}
}

func (o *Once) doSlow(f func(a)) {
	o.m.Lock()                          // lock
	defer o.m.Unlock()
	
	if o.done == 0 {                    // check
		defer atomic.StoreUint32(&o.done, 1)
		f()
	}
}
Copy the code

reference

  • I study design patterns – singleton patterns
  • Why the singleton pattern?
  • The singleton pattern
  • Singleton and serialization stuff
  • Singleton pattern of Go language