Singleton Mode--Detailed Java Version

singleton pattern


The class of a singleton object must ensure that only one instance exists

  • Lazy: Refers to the global singleton instance being constructed the first time it is used
  • Hungry Chinese style: refers to the construction of a global singleton instance at class loading time

1. Lazy singleton

1.1 Simplest version

//version1
public  class  Single1{
private  static  Single1  instance;
public  static  Single1  getInstance(){
if (instance ==  null)
instance =  new  Single1();
return instance;
}
}

Or further, make the constructor private, which prevents it from being called by external classes.

// Version 1.1
public  class  Single1 {
private  static  Single1  instance;
private  Single1() {}
public  static  Single1  getInstance() {
if (instance ==  null) {
instance =  new  Single1();
}
return instance;
}
}

This way of writing is fine most of the time.
The problem is that when working with multiple threads, if there are multiple threads running at the same time to if (instance == null), and they are all judged to be null, then the two threads will each create an instance - this way, it is not a single instance. example


1.2 synchronized version ['sɪŋkrənaɪzd]  

// Version 2
public  class  Single2 {
private  static  Single2  instance;
private  Single2() {}
public  static  synchronized  Single2  getInstance() {
if (instance ==  null) {
instance =  new  Single2();
}
return instance;
}
}

However, this way of writing also has a problem: locking the gitInstance method, although avoiding the multiple instance problems that may occur, will force all threads except T1 to wait, which will actually negatively affect the execution efficiency of the program. influence.


1.3 Double-Check version

The efficiency problem of Version2 code relative to Version1d code is actually to solve the problem of 1% chance, and use a 100% appearance shield. There is an optimization idea, which is to change the 100% appearance of the protective shield to a 1% chance of appearing, so that it only appears in places that may cause multiple instances to appear.
- Is there such a method? Of course there is. The improved code Vsersion3 is as follows:

//version3
public  class  Single3{
private  static  Single3  instance;
private  Single3(){}
public  static  Single3  getInstance(){
if (instance ==  null){
synchronized (Single3.class){
if (instance ==  null)
instance =  new  Single3();
}
}
return instance;
}
}

This version of the code looks a bit complicated, notice that there are two if (instance == null) judgments, this is called "Double-Check"

  • The first if (instance == null) is actually to solve the efficiency problem in Version2. Only when the instance is null will it enter the synchronized code segment - greatly reducing the chance.
  • The second if (instance == null), like Version2, is to prevent the possibility of multiple instances
    - this code looks flawless.
    ……
    …… —— Of course, it
    just
    “looks”, there is still a small probability of problems.
    This clarifies why there may be problems here. First, we need to figure out several concepts: atomic operations, instruction rearrangement.
    Knowledge point: What is instruction rearrangement?

    To put it simply, it is the optimization that the computer will do in order to improve the execution efficiency. The execution order of some statements may be adjusted without affecting the final result
    - that is, for non-atomic operations, in The atomic operations it splits into may be reordered without affecting the final result.
    The following passage is copied directly from Chen Hao's article (Single-instance SINGLETON design pattern in simple terms):

The main reason is the sentence singleton = new Singleton(), which is not an atomic operation. In fact, in the JVM, this sentence probably does the following three things.

1. Allocate memory to the singleton
2. Call the constructor of the Singleton to initialize the member variables to form an instance
3. Point the singleton object to the allocated memory space (the singleton is non-null after this step),
but it exists in the JVM's real-time compiler Optimization of instruction reordering. That is to say, the order of the second and third steps above is not guaranteed, and the final execution order may be 1-2-3 or 1-3-2. If it is the latter, it is preempted by thread 2 before 3 is executed and 2 is not executed. At this time, instance is already non-null (but not initialized), so thread 2 will directly return to instance, then use it, and then logically report an error.


1.4 Final version: volatile

For the problems that may occur in Version3 (of course, this probability is very small, but there are still some~), the solution is: just add the volatile keyword to the instance declaration, Version4 version:

//Version4
public  class  Single4{
private  static  volatile  Single4  instance;
private  Single4(){}
public  static  Single4  getInstance(){
if (instance ==  null){
synchronized (Single4.class){
if (instance ==  null){
instance =  new  Single4();
}
}
}
return instance;
}
}
  • volatileOne of the functions of the keyword is to prohibit 指令重排, after the instance is declared as volatile, there will be one write operation 内存屏蔽on it, so that the read operation does not need to be called before it is assigned a value.

    Note: volatile prevents the reordering of instructions in [1-2-3] inside the sentence singleton = new Singleton(), but ensures that before a write operation ([1-2-3]) is completed, no Call the read operation (if (instance == null)).


2. Hungry Chinese Singleton

As mentioned above, Hungry-style singleton refers to the way in which a global singleton instance is constructed during class loading.

2.1 Implementation of Hungry Chinese Singleton

//饿汉式实现
public  class  SingleB {
private  static  final  SingleB  INSTANCE  =  new  SingleB();
private  SingleB() {}
public  static  SingleB  getInstance() {
return INSTANCE;
}
}

For a hungry Chinese-style singleton, it's basically perfect.
So its disadvantage is just the disadvantage of the Chinese-style singleton itself - since the initialization of INSTANCE is carried out when the class is loaded, and the loading of the class is done by the ClassLoader, the developer originally initialized it Timing is difficult to grasp precisely:

  • It may be a waste of resources due to too early initialization
  • If the initialization itself depends on some other data, then it's hard to guarantee that other data will be ready before it is initialized.
    Of course, this implementation is also fine if the required singleton takes up few resources and does not depend on other data

2.2 Other implementations

2.2.1 Effective Java 1 - Static Inner Classes

// Effective Java 第一版推荐写法
public  class  Singleton {
private  static  class  SingletonHolder {
private  static  final  Singleton  INSTANCE  =  new  Singleton();
}
private  Singleton (){}
public  static  final  Singleton  getInstance() {
return  SingletonHolder.INSTANCE;
}
}

This is very cleverly written:

  • For the inner class SingletonHolder, it is a hungry singleton implementation. When SingletonHolder is initialized, the ClassLoader will ensure synchronization, so that INSTANCE is a true singleton.
  • At the same time, since SingletonHolder is an inner class, it is only used in the getInstance() of the Singleton of the outer class, so it is loaded when the getInstance() method is called for the first time.
    - It uses ClassLoader to ensure synchronization, while allowing developers to control the timing of class loading. From the inside, it is a hungry singleton, but from the outside, it is indeed a lazy implementation.

2.2.2 Effective Java 2 - Enums

// Effective Java 第二版推荐写法
public  enum  SingleInstance {
INSTANCE;
public  void  fun1() {
// do something
}
}

// 使用
SingleInstance.INSTANCE.fun1();

Did you see it? This is an enumeration type...not even a class, minimalist.
Since the process of creating an enum instance is thread-safe, there is no synchronization problem with this way of writing.

The author's evaluation of this method:

This way of writing is functionally similar to the common field method, but it is more concise and provides a serialization mechanism for free, absolutely preventing it from being instantiated, even in the face of complex serialization or reflection attacks. Although this approach has not been widely adopted, single-element enumeration types have become the best way to implement Singleton.


3. Summary

OK, seeing this, do you still think the singleton pattern is the simplest design pattern? Looking back at the singleton implementation in your previous code, do you think it is impeccable?
Maybe we don't have such strict requirements on the implementation of singleton in actual development. For example, if I can guarantee that all getInstances are in a thread, then the first and simplest textbook method is enough. For another example, sometimes, my singleton becomes multiple cases, which may not have much impact on the program...
But if we can understand more details, then if someday the program goes wrong, we can at least One more point to troubleshoot. The sooner you solve the problem, the sooner you can go home and eat... :-D

—— Also, there is no perfect solution, and any method will have a "degree" problem. For example, you think the code is impeccable, but because you are using the JAVA language, there may be some bugs in the ClassLoader... Who runs your code on the JVM, maybe the JVM itself has bugs... Your code runs on the mobile phone On, there may be a problem with the mobile phone system... You live in this universe, maybe the universe itself has some bugs... o(╯□╰)o
So, just try to do the best you can.

- Thank you for spending a lot of time here, I hope you didn't feel wasted.


4. Some useful links





Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324525754&siteId=291194637