For a detailed explanation of the singleton pattern, see the author’s blog

Design patterns part 2 creative design patterns chapter 7 singleton patterns (A: C++ implementation)

This article is a Java implementation of the singleton pattern.

There are two main classes of lazy (thread-unsafe) and hungry (thread-safe) approaches to implementing the singleton pattern using Java. The following describes the singleton pattern implemented using Java.

1. Lazy, unsafe threads

Lazy initialization: Yes Multithreaded security: No Difficulty: Easy

This is the most basic implementation, and the biggest problem with this implementation is that it does not support multithreading. Synchronized is not strictly a singleton mode because it is not locked. This approach to lazy loading is obviously not thread-safe and does not work well in multiple threads. The following implementations support multithreading, but differ in performance.

package com;

public class Singleton1 {
	private static Singleton1 instance;
	
	private Singleton1(a){}
	
	public static Singleton1 getInstance(a) {  
		if (instance == null) { 

			instance = new Singleton1();  
		}
		return instance;
	} 
	public void showMessage(a){
	      System.out.println("Singleton1!"); }}Copy the code

Testing:

package com;

public class Main {

	public static void main(String[] args) {
		// Invalid constructor
	    // Compile-time error: constructor Singleton1() is not visible
	    // Singleton1 object = new Singleton1();
	 
	    // Get the only available object
		Singleton1 object = Singleton1.getInstance();
	 
	    // Displays the messageobject.showMessage(); }}Copy the code

The result is as follows:

2. Lazy, thread-safe

Lazy initialization: Yes Multithreaded safety: Yes Difficulty: Easy

This approach is very lazy and works well in multiple threads, but it is inefficient and does not require synchronization 99% of the time. Advantages: Initialization is performed only on the first call, avoiding memory waste. Disadvantages: synchronized must be added to ensure singletons, but locking will affect efficiency. The performance of getInstance() is not critical to the application (this method is used infrequently).

package com;

public class Singleton2 {
	
	private static Singleton2 instance;
	
	private Singleton2(a){}
	
	public static synchronized Singleton2 getInstance(a) {  
		if (instance == null) { 

			instance = new Singleton2();  
		}
		return instance;
	} 
	public void showMessage(a){
	      System.out.println("Singleton2!"); }}Copy the code

Testing:

package com;

public class Main {

	public static void main(String[] args) {
		
	    // Get the only available object
		Singleton2 object1 = Singleton2.getInstance();
		Singleton2 object2 = Singleton2.getInstance();
	    // Displays the message
	    object1.showMessage();
	    object2.showMessage();
	    
	    if(object1 == object2)
	    {
	    	System.out.println("Create unique objects!");; }}}Copy the code

The result is as follows:

3. The hungry

Lazy initialization: No Multithreaded safety: Yes Difficulty of implementation: Easy to describe: This method is more common, but prone to garbage objects. Advantages: Without locking, the execution efficiency will be improved. Disadvantages: Class initialization on load, waste of memory. It avoids multithreading synchronization problems based on the ClassLoader mechanism. However, instance is instantiated when the class is loaded. Although there are many reasons for class loading, in singleton mode the getInstance method is mostly called, However, there is no other way (or static way) to load the class, so initializing instance is obviously not lazy.

package com;

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

		return instance;
	} 
	
	public void showMessage(a){
	      System.out.println("Singleton3!"); }}Copy the code

Testing:

package com;

public class Main {

	public static void main(String[] args) {
		// Get the only available object
		Singleton3 object1 = Singleton3.getInstance();
		Singleton3 object2 = Singleton3.getInstance();
	    // Displays the message
	    object1.showMessage();
	    object2.showMessage();
	    
	    if(object1 == object2)
	    {
	    	System.out.println("Create unique objects!");; }}}Copy the code

The result is as follows:

4. Double-checked locking (DCL, double-checked locking)

JDK version: JDK1.5 From Lazy initialization: Yes Multithreaded security: Yes Difficulty of implementation: complex This approach uses the double lock mechanism, safe and can maintain high performance in multithreaded situations. The performance of getInstance() is critical to the application.

package com;

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

		if (instance == null) {  
	        synchronized (Singleton4.class) {  
		        if (instance == null) {  
		        	instance = newSingleton4(); }}}return instance;  
	} 
	
	public void showMessage(a){
	      System.out.println("Singleton4!"); }}Copy the code

Testing:

package com;

public class Main {

	public static void main(String[] args) {
		
		// Get the only available object
		Singleton4 object1 = Singleton4.getInstance();
		Singleton4 object2 = Singleton4.getInstance();
	    // Displays the message
	    object1.showMessage();
	    object2.showMessage();
	    
	    if(object1 == object2)
	    {
	    	System.out.println("Create unique objects!");; }}}Copy the code

The result is as follows:

Register/static inner class

Lazy initialization: Yes Multithreaded safety: Yes Difficulty of implementation: Generally, this method can achieve the same effect of double lock, but is simpler to implement. Lazy initialization for static fields should be used instead of double-checking. This approach is only suitable for static domains, and double-check can be used when the instance domain requires delayed initialization.

This method also uses the classloader mechanism to ensure that the instance is initialized with only one thread, which differs from the third method: The third way is that the instance is instantiated as long as the Singleton class is loaded (without the lazy loading effect). In this way, the instance is not necessarily initialized when the Singleton class is loaded. Because the SingletonHolder class is not actively used, instance is instantiated by explicitly loading the SingletonHolder class only when the getInstance method is explicitly called. Imagine if instantiating instance is expensive and you want it to load lazily, but on the other hand, you don’t want to instantiate it when the Singleton class loads because there’s no guarantee that the Singleton class could be used actively elsewhere and loaded. This is obviously not the time to instantiate instance. At this point, this approach seems more reasonable than the third one.

package com;

public class Singleton5 {
	private static class SingletonHolder { 
		private static final Singleton5 INSTANCE = new Singleton5();  
	}
	private Singleton5 (a){}

	public static final Singleton5 getInstance(a) {  
		return SingletonHolder.INSTANCE;  
	}  
	
	public void showMessage(a){
	      System.out.println("Singleton5!"); }}Copy the code

Testing:

package com;

public class Main {

	public static void main(String[] args) {
		
		// Get the only available object
		Singleton5 object1 = Singleton5.getInstance();
		Singleton5 object2 = Singleton5.getInstance();
	    // Displays the message
	    object1.showMessage();
	    object2.showMessage();
	    
	    if(object1 == object2)
	    {
	    	System.out.println("Create unique objects!");; }}}Copy the code

The result is as follows:

6, the enumeration

Lazy initialization: No Multithreaded safety: Yes Difficulty of implementation: Easy This implementation is not widely adopted, but it is the best way to implement the singleton pattern. It is more concise, automatically supports serialization mechanisms, and absolutely prevents multiple instantiations. This approach, advocated by Effective Java author Josh Bloch, not only avoids multithreaded synchronization issues, but also automatically supports serialization, prevents deserialization from recreating new objects, and absolutely prevents multiple instantiations. However, since the enum feature was added later in JDK1.5, writing this way is somewhat unfamiliar and rarely used in practice. Private constructors cannot be called through Reflection Attack.

public enum Singleton {  
    INSTANCE;  
    public void whateverMethod(a) {}}Copy the code

In general, the first and second lazy ways are not recommended, and the third hungry way is recommended. The fifth registration method is used only when the lazy loading effect is explicitly implemented. If it comes to deserializing and creating objects, you can try the sixth method of enumeration. If you have other special requirements, you can consider using a fourth double-check mode.

7, the lock

After JDK1.5, the Lock interface (and related implementation classes) is added in the package to implement the Lock function. The Lock interface provides synchronization functions similar to synchronized, but requires manual Lock acquisition and Lock release. Although Lock interface does not automatically acquire and release Lock synchronization keyword so convenient, but Lock interface has Lock operability, can interrupt access and timeout access Lock and other very practical synchronization characteristics, in addition to the Lock interface has two very powerful implementation class reentrant Lock and read and write Lock.

Check out the API, check out the Lock interface description, and the Lock implementation provides a broader range of locking operations than are available with synchronized methods and statements.

class X {
   private final ReentrantLock lock = new ReentrantLock();
     public void fun(a) { 
     lock.lock();  // block until condition holds
     try {
       // ... method body
     } finally {
       lock.unlock()
     }
   }
 }
Copy the code

Lock provides a more object-oriented Lock that provides more manipulation of the Lock. Instead of synchronizing, we use the Lock interface and its Lock () and unlock() methods:

package com;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Singleton6 {

	// Create a Lock object
	static Lock ck = new ReentrantLock();
	
	private static Singleton6 instance;
	private Singleton6 (a){}

	public static Singleton6 getInstance(a) {  
		if (instance == null) { 
			ck.lock();
			if(instance == null)
			{
				instance = new Singleton6();  
			}
			ck.unlock();
		}
		return instance; 
	}  
	
	public void showMessage(a){
	      System.out.println("Singleton6!"); }}Copy the code

The result is as follows: