preface

How will the interviewer ask questions in singleton mode during the interview? How do you answer that? You may come across these questions in your interview:

  • Why are hungry singletons inherently thread-safe?
  • Why are traditional lazy singletons non-thread-safe?
  • How do you modify a traditional lazy singleton to make it thread safe?
  • What other thread-safe singleton implementations are there, and how?
  • The use of the double-checking pattern and the Volatile keyword in singleton patterns
  • Application of ThreadLocal in singleton pattern
  • Enumerated singleton

So how do we answer that? That answer came, read the following content can be with the interviewer Lao singleton mode

Introduction to singleton mode

The singleton pattern is a common software design pattern. It belongs to the creation pattern, which means that there is only one instance of a class and it provides a global access point for the whole system.

Structure:

Three elements of singleton mode:

  • Private constructor;
  • Private static instance references;
  • Returns a static public method for a static instance.

Advantages of the singleton pattern

  • There is only one object in memory to save memory space.
  • Avoid frequent creation and destruction of objects to improve performance;
  • Avoid multiple occupation of shared resources and simplify access;
  • Provide a global access point for the entire system.

Considerations for the singleton pattern

When using the singleton pattern, we must use the public factory methods provided by the singleton class to get the singleton object, and should not create it using reflection, which would break the singleton pattern and instantiate a new object.

Single threaded implementation

In a single-threaded environment, the singleton mode is divided into,

  • A hungry singleton (load immediately), which instantiates an object and refers to the instance to which the singleton class is loaded.
  • Lazy singletons (lazy loading) that instantiate the instance to which an object will refer only when needed.

Hungry-handedness is better in terms of speed and reaction time. Slacker (aka lazy loading) is better in terms of resource efficiency.

Hangry singleton

Public class HungrySingleton{// create a private static instance, Private static HungrySingleton singleton = new HungrySingleton(); // Private constructor privateHungrySingletonPublic static HungrySingleton (){} // Return the static public method of the static instancegetSingleton() {return singleton;    }}Copy the code

A hanchian singleton, which instantiates an object and refers to the instance to which the class is loaded; More importantly, because this class is only loaded once and created once in its entire life cycle, the Villain singleton is thread-safe.

So why are hungrier singletons inherently thread-safe?

Because classes are loaded on demand and only once. Because a class is loaded only once in its lifetime, the singleton is created before the thread accesses it, and there is only one instance of it. That is, a thread can only and must only get this unique object each time.

Lazy singleton

Public class LazySingleton {// private static instance LazySingleton; // Private constructor privateLazySingleton(){} // Returns the static public method of the static instance, the static factory method public static LazySingletongetSingleton(){// Create a singleton class when it needs to be created and reference the instance to which it pointsif (singleton == null) {            singleton = new LazySingleton();        }        return singleton;    }}Copy the code

Lazy singletons are lazy-loaded, instantiating an object and referring to it only when needed.

Since it is created on demand, it is unsafe in a multi-threaded environment, and instances may be created concurrently. In the case of multi-instances, the original intention of the singleton pattern is different. So how do we avoid it? You can see the implementation of the singleton pattern in multithreading below.

So why is the traditional lazy singleton non-thread-safe?

The main reason for non-thread-safe is that multiple threads can create instances (if (Singleton == NULL) {} code block) at the same time. When this happens, the singleton class creates multiple instances, which defeats the purpose of the singleton pattern. Therefore, the traditional lazy singleton is not thread-safe.

Multithreaded implementation

They work well in a single-threaded environment, both hunchman and slackhead singletons. However, variation is possible in a multi-threaded environment:

  • Hungry singletons are inherently thread-safe and can be used directly with multithreading without problems
  • Lazy singletons are inherently non-thread-safe, so multiple instances can occur, contrary to the intent of the singleton pattern.

So how do we build on the lazy?

  • Synchronized methods
  • Synchronized blocks
  • Lazy loading is implemented using inner classes

Synchronized methods

Public class SynchronizedSingleton {private static SynchronizedSingleton SynchronizedSingleton; privateSynchronizedSingletonPublic static synchronized SynchronizedSingleton public static synchronized SynchronizedSingletongetSingleton() {if (synchronizedSingleton == null) {            synchronizedSingleton = new SynchronizedSingleton();        }        return synchronizedSingleton;    }}Copy the code

Use synchronized to modify getSingleton() method, lock getSingleton() method, realize synchronous mutually exclusive access to critical resources, so as to ensure singleton.

Although thread safety is realistic, the operation efficiency will be low due to the large scope of synchronization and coarse granularity of lock.

Synchronized blocks

Public class BlockSingleton {private static BlockSingleton; privateBlockSingleton(){}    public static BlockSingleton getSingleton2(){synchronized(blocksingleton.class){// Use synchronized blocks to access critical resources mutually exclusiveif(singleton == null) { singleton = new BlockSingleton(); }}return singleton;    }}Copy the code

In fact, synchronized blocks are similar to synchronized methods, with low efficiency.

Lazy loading is implemented using inner classes

Public class InsideSingleton {// Private inner class, load on demand, load on time, Private static class Holder {private static InsideSingleton = new InsideSingleton(); } privateInsideSingleton() {    }    public static InsideSingleton getSingleton() {        return Holder.insideSingleton;    }}Copy the code
  • As shown in the code above, it is also more efficient to implement thread-safe lazy singletons using inner classes. The principle is the same as hunchman singleton, but there may also be reflection attacks or deserialization attacks.

Double-check idiom indicates the reality

Double-check idiom -volatile

Using double detection synchronous lazy loading to create singletons not only ensures singletons, but also improves program efficiency.

Public class DoubleCheckSingleton {// Use the volatile keyword to prevent reordering, since new Instance() is a non-atomic operation, May create an incomplete instance private static Volatile DoubleCheckSingleton singleton; privateDoubleCheckSingleton() {    }    public static DoubleCheckSingleton getSingleton() {        // Double-Check idiom        if(singleton = = null) {synchronized (DoubleCheckSingleton. Class) {/ / just synchronization in the first instance is createdif(singleton == null) { singleton = new DoubleCheckSingleton(); }}}return singleton;    }}Copy the code

In order to improve performance without singletons, we need to do a second check of the Singleton instance to avoid excessive synchronization (because synchronization only takes place when the instance is first created, and once it is successfully created, there is no need for synchronous acquisition locks for subsequent instances).

It is important to note that the singleton reference must be volatile. Why?

If the volatile keyword is not used, instruction reordering may occur. Before the Singleton constructor body is executed, the variable Singleton may become non-null, i.e. the assignment statement is called before the object is instantiated. Other threads will get an incomplete (uninitialized) object, causing the system to crash.

This may be a program execution step:

  1. Thread 1 enters the getSingleton() method. Since singleton is null, thread 1 enters the synchronized block.
  2. If singleton = new DoubleCheckSingleton(), thread 1 will proceed directly to singleton = new DoubleCheckSingleton(). And the instance is not initialized (for reasons in NOTE);
  3. At this point, thread 2 checks to see if the instance is null. Since the instance is not NULL, thread 2 gets an incomplete (uninitialized) Singleton object;
  4. Thread 1 initializes the Singleton object by running its constructor.

This kind of security risk is caused by the instruction reordering problem. The volatile keyword solves this problem perfectly. Using the volatile keyword to modify singleton references can avoid this disaster.

NOTE

The new operation takes three steps. The expected execution steps are:

memory = allocate(); //1: allocate the object’s memory space ctorInstance(memory); //2: initialize singleton = memory; //3: make singleton3 point to the newly allocated memory address

In practice, however, this process can be written out of order (instruction reordering), which can result in the following execution steps:

memory = allocate(); Singleton3 = memory; //3: set singleton3 to the newly allocated memory ctorInstance(memory); //2: initializes the object

Double-check idiom -ThreadLocal

With ThreadLocal, we can implement a variant of the double-checked pattern. We localize the critical resource thread, in this case by converting the first-level check condition if (instance == NULL) of the double check to a thread-local operation.

Private static ThreadLocal<ThreadLocalSingleton> private static ThreadLocal<ThreadLocalSingleton> threadLocal = new ThreadLocal<ThreadLocalSingleton>(); private static ThreadLocalSingleton singleton = null; privateThreadLocalSingleton(){}    public static ThreadLocalSingleton getSingleton() {ifThreadlocal.get () == null) {// First check whether this thread is accessing createSingleton() for the first time; }return singleton;    }    public static void createSingleton(){        synchronized (ThreadLocalSingleton.class) {            ifSingleton = new ThreadLocalSingleton(); singleton = new ThreadLocalSingleton(); }} threadlocal.set (singleton); // Put the singleton into the local variable of the current thread}}Copy the code

With ThreadLocal, we can also implement thread-safe lazy singletons. However, the implementation using ThreadLocal is not as efficient as double-checked locking when used in direct double-checked mode.

Enumeration implementation

Not only does it avoid multithreaded synchronization issues, but it also prevents deserialization from recreating new objects,

Directly through the Singleton. INSTANCE. WhateverMethod () call. Convenient, simple and safe.

public enum EnumSingleton {    instance;    public void whateverMethod(){        //dosomething    }}Copy the code

Test singleton thread safety

Use multiple threads and calculate the value of each instance using the hashCode value. If the same value is the same instance, otherwise it is different.

public class Test {    public static void main(String[] args) {        Thread[] threads = new Thread[10];        for (int i = 0; i < threads.length; i++) {            threads[i] = new TestThread();        }        for(int i = 0; i < threads.length; I++) {threads [I] the start (); } }}class TestThread extends Thread { @Override public voidrun() {// For implementations of different singleton patterns, just change the corresponding singleton class name and its public static factory method name to inthash = Singleton5.getSingleton5().hashCode();          System.out.println(hash);    }}Copy the code

summary

The singleton pattern is one of the simplest, most basic, and most commonly used design patterns in Java. During runtime, ensure that only one instance of a class is created, ensure that only one instance of a class is created, and provide a global access point to access it.

  • Hungry singleton (thread-safe)
  • Lazy singleton Traditional lazy singleton (thread-safe); Use synchronized methods real (thread-safe); Synchronized blocks for lazy singletons (thread-safe); Lazy singletons with static inner classes (thread-safe).
  • Use the volatile keyword (thread-safe) in double-checking mode; Use ThreadLocal for lazy singletons (thread-safe).
  • Enumerated singleton

Is everyone still ok? If you like, move your hands to show, point a concern bai!! Thanks for your support!