Correct implementation of Kotlin static inner class singleton pattern

Recently, when sorting out the basic library, a basic class is needed to store the initialized data, such as the Application Context of the application, the user's login token and other information. These are basically the information of the global class of the application, which will be used in the entire life cycle of the application. To, so I designed this base class as a singleton mode to optimize performance.

There are 6 singleton patterns that I know, hungry man style, lazy man style, thread-safe lazy man style, volatile + double check lock test, static inner class style, and enumeration style.

Which one should I choose? That must be the best performance!

The performance of a singleton mode mainly depends on whether it can guarantee the uniqueness of the instance in a complex production environment .

The production environments that may be encountered are as follows. If the uniqueness of the instance cannot be guaranteed under the following circumstances, then the singleton mode is flawed.

  1. Whether it will be created multiple times under multi-threaded conditions.

  2. Whether the instance can be recreated when reflection calls the constructor of the singleton class.

  3. If the singleton class implements the Serializable interface, whether the instance generated during deserialization is the same as the instance during serialization.

Enumeration is said to be the safest and best-performing singleton mode. Java does not allow reflection to call the constructor of the enumeration class, and has also made special treatment for the serialization process of the enumeration class. At the same time, the enumeration class uses language features to ensure Multi-thread safety.

However, I use very little enumeration, and I am not used to it personally. I chose the most convenient static inner class, and I use this singleton mode the most.

However, the static inner class singleton mode is flawed . The static inner class uses the class loading mechanism to ensure multi-thread safety, but its construction method can still be called externally through reflection. If the class implements the Serializable interface, the object generated by its default deserialization process is also different from the object during serialization.

Although there are flaws, there are solutions.

To solve the problem that the object may be regenerated when the reflection calls the construction method, we only need to add a flag bit check to the construction method of the class to solve it:

class Common private constructor(){
    
    companion object {
    	private var flag = false
    }

    // 防止反射破坏单例
    init {
        if (!flag) {
            flag = true
        } else {
            throw Throwable("SingleTon is being attacked.")
        }
    }
}

The problem of deserialization can be solved by declaring readResolve()the method and replacing it with our singleton before deserializing and returning the object.

fun readResolve(): Any {
    return Common.getInstance()
}

More knowledge about solving the defects of the singleton mode will not be expanded here. Students who are interested can surf the Internet by themselves.

Considering my singleton class usage scenario, there is no need to implement the serialization interface, so it is only necessary to solve the problem of reflection calling.

The defect is solved, so now let’s implement a static inner class singleton mode for my singleton class~

Static inner class singleton mode, as a Java veteran, I can write it in Java at my fingertips, directly typing with a document editor without looking at it:

// java 实现静态内部类单例
class Common {
  private static boolean flag = false;
  
  // 解决反射调用问题
  private Common() {
    if (!flag) {
      flag = true
    } else {
      throw new Throwable("SingleTon is being attachked.")
    }
  }
	
  public static final Common getInstance() {
	  return CommonSingleTonHolder.sInstance
  }
	
  private static final class CommonSingleTonHolder {
	  private static Common sInstance = new Common();
  }
}

The problem of reflection calling was also solved between raising hands~

Why is the singleton pattern of static inner classes thread-safe? Here is a brief mention of the class loading mechanism

Simply put, the class loading process includes five processes: loading, verification, preparation, parsing, and initialization.

  1. Loading : The virtual machine obtains the binary byte stream of the class through the fully qualified class name of the class, converts the static storage structure represented by this byte stream into the runtime data storage structure in the method area, and generates a class object in the heap, as the entry point to access this runtime data storage structure.
  2. Verification : Whether the byte stream file of the virtual machine verification class conforms to the specification of the virtual machine, and whether it will affect the security of the virtual machine. It mainly includes file format verification, metadata verification, bytecode verification, and symbol reference verification.
  3. Preparation : allocate heap memory for static variables in the class and initialize them to default values
  4. Parsing : Convert the symbolic references in the Class file into direct references to memory.
  5. Initialization : Execute the clinit method of the class constructor, which includes the assignment operation of the static variables of the class and the static statement block.

Static variables of the class are allocated memory during the prepare phase and initialized to default values. <clinit>()In the initialization phase, the method of the class will be executed , the assignment operation of the static variable and the static statement block will be executed.

The virtual machine ensures that the methods of a class <clinit>()are correctly locked and synchronized in a multi-threaded environment. If multiple threads initialize a class at the same time, only one thread will execute the () <clinit>method of this class, and other threads need to be blocked. Waits until the active thread <clinit>()finishes executing the method. Therefore, singletons of static inner classes are also thread-safe when accessed by multiple threads.

The singleton mode of the static inner class is also a lazy mode . Only Common.getInstance()when , the CommonSingleTonHolder class will be loaded to sInstanceinitialize the static properties.  

Kotlin implements static inner class singleton pattern

The previous article introduced so much about the implementation of Java, how to implement it with Kotlin?

In line with the principle of reaching out and never doing it yourself, search the Internet, if you can use it directly, copy it directly~

Kotlin static internal class singleton mode uploaded on the live network

// google 第一页搜索内容,截止到 2022 年 1 月 14 日
class SingletonDemo private constructor() {
    companion object {
        val instance = SingletonHolder.holder
    }
 
    private object SingletonHolder {
        val holder = SingletonDemo()
    }
}

Click the first item after searching, and return to the above search results.

Oh, it looks very simple, see if there are other implementations, click on the second and third search content one after another, and find that a whole page of static internal class singletons returns such results.

Everyone is the same, well, it's just you. ctrl + c, ctrl + v, change the class name, nice, done.

class Common private constructor(){

    // 防止反射破坏单例
    init {
        if (!flag) {
            flag = true
        } else {
            throw Throwable("SingleTon is being attacked.")
        }
    }

    companion object {
    	private var flag = false
    	// 单例
		val instance = CommonSingletonHolder.holder
    }

    /**
     * 静态内部类单例
     */
    private object CommonSingletonHolder {
        val holder = Common()
    }

}

Common.instanceWe can access our singleton through . Perfect, compare the implementation of Kotlin and Java, very similar!

Decompile the above Kotlin code into Java , and then look at it, but I feel something is wrong.

In order to make the code generated by Kotlin decompilation the same as the Java native calling method, I added @JvmField annotation to the instance attribute before decompilation.

companion object {
    @JvmField
    val instance = CommonSingletonHolder.holder
}

Then decompile it into Java code, omitting the code irrelevant to the analysis:

public final class Common {
   @JvmField
   @NotNull
 	// 单例
   public static final Common instance;

   private Common() {}

 	// 单例的赋值
   static {
      instance = Common.CommonSingletonHolder.INSTANCE.getHolder();
   }

   // $FF: synthetic method
   public Common(DefaultConstructorMarker $constructor_marker) {
      this();
   }

   private static final class CommonSingletonHolder {
      @NotNull
      private static final Common holder;
      @NotNull
      public static final Common.CommonSingletonHolder INSTANCE;

      @NotNull
      public final Common getHolder() {
         return holder;
      }

      static {
         Common.CommonSingletonHolder var0 = new Common.CommonSingletonHolder();
         INSTANCE = var0;
         holder = new Common((DefaultConstructorMarker)null);
      }
   }

   ...
}

It doesn't seem right! Take a closer look at the singleton assignment code , it is initially assigned in the static static code block.

As mentioned above, the static inner class is essentially a lazy singleton mode. If the instance is initialized in the static code block, then the instance will be initialized during the loading of the Common class. In essence, it will become Is it a hungry singleton pattern?

Write a test case to verify when the instance is initialized:

class Common private constructor(){

    init {
        JLog.d("Common", "init Common.")
    }

    companion object {
    	private var flag = false
    	
        @JvmField
        val instance = CommonSingletonHolder.holder

        fun test() {
            JLog.d("Common", "Common test called.")
        }
    }

    /**
     * 静态内部类单例
     */
    private object CommonSingletonHolder {
        val holder = Common()
    }

}

// 执行 test() 方法
Common.test()

Call Common.test()and run to see, if it is lazy mode, as long as the Common does not execute to CommonSingletonHolder.holder, it will not trigger the loading of the CommonSingletonHolder class, let alone val holder = Common()complete the initialization of the instance.

2022-01-14 14:29:11.330 3500-3500/com.jamgu.common D/Common: init Common.
2022-01-14 14:29:11.330 3500-3500/com.jamgu.common D/Common: Common test called.

Bingo! The log is printed first init Common. Instance is initialized when the Common class is loaded! , what we have implemented is actually a starving pattern of static inner classes .

Ah, what is the name of the singleton mode that is both hungry man mode and static inner class mode? ? Hungry for static? It sounds like it makes sense. .

Haha, just kidding, let’s get down to business, the code we implemented is neither a static inner class pattern nor a hungry man pattern, so how should we modify it to conform to an authentic static inner class pattern?

Easy, the static properties and static code blocks of the class will be assigned and executed during the initialization phase of class loading, but the static method of the class will not. Using the singleton as the return value of the static method can perfectly make the singleton lazy loaded .

companion object {
    @JvmStatic
  	// 修改此处
    fun getInstance() = CommonSingletonHolder.holder

    fun test() {
        JLog.d("Common", "Common test called.")
    }
}

Execute again Common.test()to see the result:

2022-01-14 14:46:34.914 3862-3862/com.jamgu.common D/Common: Common test called.

The initialization of the singleton is not executed, get it~

Correct implementation of Kotlin static inner class singleton pattern

Finally, a safe static inner class singleton pattern implemented in Kotlin is brand new.

class Common private constructor(){

    // 防止反射破坏单例
    init {
        if (!flag) {
            flag = true
        } else {
            throw Throwable("SingleTon is being attacked.")
        }
    }

    companion object {
    	private var flag = false
    	
        @JvmStatic
        fun getInstance() = CommonSingletonHolder.holder
    }

    /**
     * 静态内部类单例
     */
    private object CommonSingletonHolder {
        val holder = Common()
    }

}

The version of the static internal class uploaded on the Internet is not accurate. Only some modifications are needed to realize the real singleton mode of the static internal class~

@JvmField
val instance = CommonSingletonHolder.holder
// 改成 ----->>>>>
@JvmStatic
fun getInstance() = CommonSingletonHolder.holder

 

Guess you like

Origin blog.csdn.net/m0_70748845/article/details/129904002