Are enumerating singletons better than DCL and static singletons? That's true~

Let's not talk about the hungry and lazy single examples. DCL and static single examples are briefly introduced to pave ...
1.1 why double check? Can you get rid of the second check?
1.2 why add volatile keyword to singleton?
3.1 use of enumeration singleton mode
3.2 decompile and analyze singleton enumeration classes

Let's not talk about the hungry and lazy single examples. DCL and static single examples are briefly introduced to pave the way for the later explanation of enumeration single examples. It's not easy to analyze. Welcome to click three times~

1. Single example of double check lock (DCL)
public class Singleton { private static volatile Singleton singleton; private Singleton(){ } public static Singleton getInstance(){ if (singleton == null){ synchronized (Singleton.class){ if (singleton == null){ singleton = new Singleton(); } } } return singleton; } }

The advantages of this DCL writing method are not only thread safety, but also delayed loading.

1.1 why double check? Can you get rid of the second check?

   of course not. When two threads execute the getInstance method at the same time, the first if judgment will be executed. Due to the existence of the locking mechanism, one thread will enter the synchronization statement first and the other thread will wait. After the first thread executes new Singleton(), it will exit the synchronized protection area. At this time, if there is no second if judgment, Then the second thread will also create an instance, which destroys the singleton.

1.2 why add volatile keyword to singleton?

The main reason is singleton = new Singleton(); Not an atomic operation.

In the JVM, this statement does at least three things

  • 1. Allocate memory space to Singleton instances;
  • 2. Call the constructor of Singleton() to initialize the member field;
  • 3. Point the singleton to the allocated memory space (at this time, the singleton is not null)

Because of the optimization of instruction reordering, the order of steps 2 and 3 cannot be guaranteed. The final execution order may be 1-2-3 or 1-3-2. If the execution order is 1-3-2, let's see what problems will occur

    although the singleton is not null, the space pointed to is not initialized, and an error will still be reported. This is the problem of DCL failure. This problem is difficult to track and reproduce, and may be hidden for a long time.

   the write back regulations from Cache and register to main memory in JMM(Java Memory Model) before JDK1.5 cannot be guaranteed in the order of the second and third above. After JDK 1.5, SUN officially adjusted the JVM to specify the volatile keyword, private static volatile Singleton; As long as volatile is added, it can ensure that it can be read from main memory every time (this involves the consistency of CPU Cache, which is not within the scope of this paper, and is interested in searching by itself). It can also prevent instruction reordering and avoid getting objects that have not completed initialization.

2. Static internal class singleton
public class Singleton{ private Singleton(){} private static class SingletonInstance { private static Singleton singleton = new Singleton(); } public static Singleton getInstance(){ return SingletonInstance.singleton; } }

The difference from the hungry Chinese style is that when the class is loaded, the object will not be instantiated here. The object will be instantiated only by calling the getInstance method.
Like DCL, it has the advantages of delayed loading and high efficiency.

Although DCL and static singleton are good, they do not prevent deserialization and reflection from generating multiple instances. Of course, a better way to write is to enumerate single examples!

3. Enumerate singletons (recommended!!)

All other ways of implementing singletons are actually problematic, that is, they may be destroyed by deserialization and reflection.

Let's take a look at the enumeration class added in JDK 1.5 to implement the singleton

public enum Singleton { INSTANCE, public void testMethod() { } }

Advantages of enumeration:

  • 1. The code is concise and elegant without considering lazy loading and thread safety

  • 2. Thread safety

       decompile any enumeration class. You will find that the enumeration items in the enumeration class are defined and initialized through static code blocks (see the analysis of decompile analysis in Section 3.2 below). They will complete initialization when the class is loaded, and the JVM ensures thread safety when loading java classes. Therefore, creating an Enum type enumeration is thread safe

  • Prevent damage to single case

  we know that serialization can write an instance object of a singleton to disk, and then deserialize it to read it back, so as to obtain a new instance. Even if the constructor is private, you can still create a new instance of the class in a special way during deserialization, which is equivalent to calling the constructor of the class.

   Java specifies the serialization of enumeration. During serialization, only the name attribute of the enumerated object is output to the result. During deserialization, you use the valueOf of java.lang.Enum to find an object according to its name instead of creating a new object. Enumeration does not call constructor when serializing and deserializing, which prevents singleton destruction caused by deserialization.

  for reflection destruction singletons, enumeration classes have the same defense measures. When creating objects through newInstance, reflection will check whether this class is an enumeration class. If so, an exception java.lang.illegalargumentexception: cannot create enum objects reflectively, indicating that reflection failed to create objects.

To sum up, enumeration can prevent deserialization and reflection from destroying singletons.

3.1 use of enumeration singleton mode

// Singleton.java public enum Singleton { INSTANCE; public void testMethod() { System.out.println("The method of the singleton class is executed"); } } // Test.java public class Test { public static void main(String[] args) { //A singleton class that demonstrates how to use enumeration writing Singleton.INSTANCE.testMethod(); System.out.println(Singleton.INSTANCE); } }

The operation results are as follows:

3.2 decompile and analyze singleton enumeration classes

    in order to let you know more about enumeration classes, we decompile javap -p Singleton.class by enumerating singleton classes above, where - p means that private methods should be included during decompilation.

// This is the decompiled content public final class Singleton extends java.lang.Enum<Singleton> { public static final Singleton INSTANCE; private static final Singleton[] $VALUES; public static Singleton[] values(); public static Singleton valueOf(java.lang.String); private Singleton(); public void testMethod(); static {}; }

As we can see,

  • INSTANCE is an INSTANCE of the Singleton class
  • Singleton inherits the java.lang.Enum class
  • There is also a private Singleton parameterless constructor. The enumeration items of the enumeration class will be instantiated using this constructor, that is, the INSTANCE here will be instantiated using this constructor.
  • The instantiation process takes place in the last empty static code block. You can further analyze the bytecode content in static through other parameters of javap. Static actually contains many bytecode instructions. These instructions are used to initialize the enumeration item INSTANCE, and the static code block is executed when the class is loaded, that is, when the Singleton class is loaded, INSTANCE is initialized. In the static code block, in addition to initializing INSTANCE, the private array defined by Singleton[] $VALUES is also created and initialized in static. Then put all enumeration items into the $values array in the defined order. Finally, we can access the array through the values method

   in order to analyze the operations in each method, we use javap -p -c -v Singleton.class to see more details, - c to see the bytecode in each method, and - v to print out the constant pool information. You can understand it here. If you don't understand it, you can see my above conclusion. The key point is to look at the bytecode in the static code block. The following is to verify the conclusion.

/** * @author: Brick ocean__ * @description: I'll focus on the last static part */ public final class Singleton extends java.lang.Enum<Singleton> minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM Constant pool: // You need to pay attention to the part of constant pool. You can go back to it later when analyzing each instruction #1 = Fieldref #4.#37 // Singleton.$VALUES:[LSingleton; #2 = Methodref #38.#39 // "[LSingleton;".clone:()Ljava/lang/Object; #3 = Class #17 // "[LSingleton;" #4 = Class #40 // Singleton #5 = Methodref #13.#41 // java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum; #6 = Methodref #13.#42 // java/lang/Enum."<init>":(Ljava/lang/String;I)V #7 = Fieldref #43.#44 // java/lang/System.out:Ljava/io/PrintStream; #8 = String #45 / / the method of the singleton class is executed #9 = Methodref #46.#47 // java/io/PrintStream.println:(Ljava/lang/String;)V #10 = String #14 // INSTANCE #11 = Methodref #4.#42 // Singleton."<init>":(Ljava/lang/String;I)V #12 = Fieldref #4.#48 // Singleton.INSTANCE:LSingleton; #13 = Class #49 // java/lang/Enum #14 = Utf8 INSTANCE #15 = Utf8 LSingleton; #16 = Utf8 $VALUES #17 = Utf8 [LSingleton; #18 = Utf8 values #19 = Utf8 ()[LSingleton; #20 = Utf8 Code #21 = Utf8 LineNumberTable #22 = Utf8 valueOf #23 = Utf8 (Ljava/lang/String;)LSingleton; #24 = Utf8 LocalVariableTable #25 = Utf8 name #26 = Utf8 Ljava/lang/String; #27 = Utf8 <init> #28 = Utf8 (Ljava/lang/String;I)V #29 = Utf8 this #30 = Utf8 Signature #31 = Utf8 ()V #32 = Utf8 testMethod #33 = Utf8 <clinit> #34 = Utf8 Ljava/lang/Enum<LSingleton;>; #35 = Utf8 SourceFile #36 = Utf8 Singleton.java #37 = NameAndType #16:#17 // $VALUES:[LSingleton; #38 = Class #17 // "[LSingleton;" #39 = NameAndType #50:#51 // clone:()Ljava/lang/Object; #40 = Utf8 Singleton #41 = NameAndType #22:#52 // valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum; #42 = NameAndType #27:#28 // "<init>":(Ljava/lang/String;I)V #43 = Class #53 // java/lang/System #44 = NameAndType #54:#55 // out:Ljava/io/PrintStream; #45 = Utf8 executes the method of the singleton class #46 = Class #56 // java/io/PrintStream #47 = NameAndType #57:#58 // println:(Ljava/lang/String;)V #48 = NameAndType #14:#15 // INSTANCE:LSingleton; #49 = Utf8 java/lang/Enum #50 = Utf8 clone #51 = Utf8 ()Ljava/lang/Object; #52 = Utf8 (Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum; #53 = Utf8 java/lang/System #54 = Utf8 out #55 = Utf8 Ljava/io/PrintStream; #56 = Utf8 java/io/PrintStream #57 = Utf8 println #58 = Utf8 (Ljava/lang/String;)V { public static final Singleton INSTANCE; // Define enumeration items descriptor: LSingleton; flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM private static final Singleton[] $VALUES; // Define array descriptor: [LSingleton; flags: ACC_PRIVATE, ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC public static Singleton[] values(); descriptor: ()[LSingleton; flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=0, args_size=0 0: getstatic #1 // Field $VALUES:[LSingleton; 3: invokevirtual #2 // Method "[LSingleton;".clone:()Ljava/lang/Object; 6: checkcast #3 // class "[LSingleton;" 9: areturn LineNumberTable: line 1: 0 public static Singleton valueOf(java.lang.String); descriptor: (Ljava/lang/String;)LSingleton; flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: ldc #4 // class Singleton 2: aload_0 3: invokestatic #5 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum; 6: checkcast #4 // class Singleton 9: areturn LineNumberTable: line 1: 0 LocalVariableTable: Start Length Slot Name Signature 0 10 0 name Ljava/lang/String; private Singleton(); descriptor: (Ljava/lang/String;I)V flags: ACC_PRIVATE Code: stack=3, locals=3, args_size=3 0: aload_0 // The stack operation instruction loads the variable load at position 0 in the local method table onto the stack. The a prefix indicates that it is a reference type. // Reminder: when the JVM executes a piece of code, it will first store all the variables used in a local variable table - local variable table. // When calculating on the stack, you need to use the values of the local method table, and they will be loaded onto the stack through the load instruction // After the operation on the stack, the value needs to be saved back to the local method table, so there will also be a corresponding store instruction, and load and store correspond. 1: aload_1 2: iload_2 3: invokespecial #6 // Method java/lang/Enum."<init>":(Ljava/lang/String;I)V 6: return LineNumberTable: line 1: 0 LocalVariableTable: Start Length Slot Name Signature 0 7 0 this LSingleton; Signature: #31 // ()V public void testMethod(); descriptor: ()V flags: ACC_PUBLIC Code: stack=2, locals=1, args_size=1 0: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #8 / / String executes the method of the singleton class 5: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LineNumberTable: line 5: 0 line 6: 8 LocalVariableTable: Start Length Slot Name Signature 0 9 0 this LSingleton; static {}; descriptor: ()V. // Is to return the void type flags: ACC_STATIC Code: stack=4, locals=0, args_size=0 0: new #4 // class Singleton // new #4 means to get the name of the type labeled 4 from the Constant pool. Looking up at the definition of the Constant pool part, we can see that it is the Singleton class, and then new comes out and becomes an object 3: dup // Then dup stack 4: ldc #10 / / String INSTANCE: push the value INSTANCE of string type labeled 10 in the constant pool to the top of the stack 6: iconst_0 // Define a variable of type int with a value of 0. I don't know what use it is to define a constant here 7: invokespecial #11 / / method "< init >": (ljava / Lang / string; I) V, call the constructor to initialize, and the return type is void 10: putstatic #12 // Field INSTANCE:LSingleton; Assign a value to the static variable instance, which is the same as name 13: iconst_1 // Define a variable of type int with a value of 1, then merge it 14: anewarray #4 / / class Singleton, create an array containing Singleton enumeration types, here is the $VALUES array 17: dup 18: iconst_0 19: getstatic #12 // Field INSTANCE:LSingleton; Get the name value of the field instance 22: aastore 23: putstatic #1 / / field $VALUES: [lsingleton; put the name VALUES of enumeration items in the local variable table into the $VALUES array in turn 26: return LineNumberTable: line 2: 0 line 1: 13 }

   in fact, the compiled bytecode is for the JVM. The JVM only needs to execute in a mindless order. Many aspects involve a lot of contents of the JVM, which is irrelevant to the topic of this article. A separate article on bytecode instructions will be opened later. Here we mainly look at the instructions in the static code block and understand why enumeration is thread safe.


Welcome to triple CLICK~

If you have any questions, please leave a message. Let's discuss and study together

----------------------Talk is cheap, show me the code-----------------------

31 October 2021, 17:44 | Views: 5470

Add new comment

For adding a comment, please log in
or create account

0 comments