JAVA implements the singleton pattern in four ways and some features

  • 2020-04-01 01:34:17
  • OfStack

A, hungry han type singleton class


public class Singleton  
{  
    private Singleton(){  

    }  

    private static Singleton instance = new Singleton();  

    private static Singleton getInstance(){  
        return instance;  
    }  
}  

Features: hungry ahead of time instantiation, no lazy lazy multithreading problem, but whether we call getInstance() or not there will be an instance in memory

Two, the inner class singleton class


public class Singleton     
{        
        private Singleton(){     

    }     

    private class SingletonHoledr(){     
        private static Singleton instance = new Singleton();     
    }     

    private static Singleton getInstance(){     
        return SingletonHoledr.instance;     
    }     
} 

Features: in the inner class, lazy loading is implemented, only when we call getInstance(), will only create a unique instance into memory.

Lazy singleton class


public class Singleton     
{        
    private Singleton(){     

    }     

    private static Singleton instance;     
    public static Singleton getInstance(){     
        if(instance == null){     
            return instance = new Singleton();     
        }else{     
            return instance;     
        }     
    }     
}   

Feature: in lazy style, there are threads A and b. when thread A runs to line 8, it jumps to thread b. when B also runs to line 8, the instance of both threads is empty, so two instances are generated. The solution is to synchronize:

Synchronization is possible but not efficient:


public class Singleton     
{        
    private Singleton(){     

    }     

    private static Singleton instance;     
    public static synchronized Singleton getInstance(){     
        if(instance == null){     
            return instance = new Singleton();     
        }else{     
            return instance;     
        }     
    }     
} 

This is not a bad way to write a program because the whole getInstance is a "critical section" of the whole thing, but it's not very efficient because the goal is to lock the first instance and not to synchronize the threads when fetching the instance.

So the smart people came up with the following solution:

Double check lock writing method:


public class Singleton{  
  private static Singleton single;    //A variable that declares a static singleton object & NBSP;
  private Singleton(){}    //Private constructor & NBSP;  

  public static Singleton getSingle(){    //This method is used externally to get an object & NBSP;    
    if(single == null){     
        synchronized (Singleton.class) {   //Ensures that only one object can access this synchronized block at a time & NBSP;            
            if(single == null){      
                single = new Singleton();          
        }     
      }  
    }    
    return single;   //Returns the created object & NBSP;  
  }  
}  

The idea is simply that we need only synchronize to initialize the instance part of the code so that it is both correct and efficient.
This is the so-called "double-check lock" mechanism (as the name implies).
Unfortunately, this is the wrong way to write on many platforms and optimization compilers.

The reason: instance = new Singleton() is a line of code whose behavior on different compilers is unpredictable. An optimized compiler can legally implement instance = new Singleton() as follows:

1. Instance  = allocate memory to the new entity

2. Call Singleton's constructor to initialize the member variables for instance

Now imagine threads A and B calling getInstance, thread A enters first, and gets kicked out of the CPU when it executes step 1. Then thread B enters, and B sees the instance ; It is no longer null (memory has been allocated), so it begins to use instance safely, but this is wrong, because at this point, the member variables of instance are still default values, and A has not yet executed step 2 to complete the instance initialization.

Of course, the compiler can also achieve this:

1. Temp = allocate memory

2. Call the constructor of temp

3. The instance = temp

If the compiler behaves like this, we seem to have no problem, but the truth is not that simple, because we cannot know exactly how a compiler does it, because the problem is not defined in the Java memory model.

Double-check locks are available for base types such as int. Obviously, because the underlying type does not call the constructor step.


Related articles: