Kerrigan is a crucial soul character for Zerg, countless drone, zergling, hydralisk ...... It can be created and sacrificed, But Kerrigan is related to Zerg's survival in this game, and Kerrigan cannot be created multiple times, there must be an instance with only one worm blade queen. This is not a game rule, but it is a political issue.
Analysis:
As before, we still try to use code to describe the process of accessing Kerrigan. Let's look at the UML diagram below, which is so simple that I am not very interested in the layout.
The structure is simple, but we still have some small requirements as follows:
1. Basic Requirement: each time you get from getinstance (), one and only one Kerrigan object is returned.
2. A little higher requirement: Kerrigan is very busy and many people look for it, so I hope this method can adapt to multi-thread concurrent access.
3. A higher requirement: Zerg is a society that focuses on civil servant efficiency and hopes to find Kerrigan as high as possible.
4. The last requirement is Kerrigan's own proposal: Understanding Kerrigan is too tired to spend more time sleeping, Kerrigan wants to implement lazy load and be constructed only when needed.
5. I originally intended to explain how to handle multiple classloader, multiple JVM, and other situations. But I still don't want to take the case into consideration too complicated. Let's leave the author blank for the moment (-_-#).
The Singleton mode we wrote for the first time is as follows:
Java code
- /**
- * The first attempt to access Kerrigan from a single instance
- */
- Public class singletonkerrigana {
- /**
- * Singleton object instance
- */
- Private Static singletonkerrigana instance = NULL;
- Public static singletonkerrigana getinstance (){
- If (instance = NULL) {// line
- Instance = new singletonkerrigana (); // line B
- }
- Return instance;
- }
- }
/*** The first attempt to implement single-instance access to Kerrigan */public class singletonkerrigana {/*** singletonkerrigana instance */Private Static singletonkerrigana instance = NULL; public static singletonkerrigana getinstance () {If (instance = NULL) {// line a instance = new singletonkerrigana (); // line B} return instance ;}} |
In this way, we can check the four requirements from the top down and find that there is a problem at the second point. Assume that two threads call singletonkerrigana concurrently. getinstance (). If the thread first checks whether the instance is null, line A in the Code enters the position of line B. After the judgment, JVM switches the CPU resource to thread 2. Because thread 1 has not executed line B, the instance is still empty, so thread 2 executes new signletonkerrigana () operation. After a moment, when the thread is awakened again, it still executes the new signletonkerrigana () operation. Well, the question is, who are the two Kerrigan, and who is the ghost?
Next we will try the singleton mode for the second time:
Java code
- /**
- * Implement the second attempt to access Kerrigan from a single instance
- */
- Public class singletonkerriganb {
- /**
- * Singleton object instance
- */
- Private Static singletonkerriganb instance = NULL;
- Public synchronized static singletonkerriganb getinstance (){
- If (instance = NULL ){
- Instance = new singletonkerriganb ();
- }
- Return instance;
- }
- }
/*** Second attempt to access Kerrigan in a single instance */public class singletonkerriganb {/*** singletonkerriganb instance */Private Static singletonkerriganb instance = NULL; public synchronized static singletonkerriganb getinstance () {If (instance = NULL) {instance = new singletonkerriganb ();} return instance ;}} |
A Synchronized modifier is added only to the method of the first code, so it can be ensured that there will be no thread problems. However, there is a large (at least a large time-consuming ratio) performance problem. In addition to the singletonkerriganb constructor executed during the first call, the instance object is directly returned for each subsequent call. The operation to return objects takes a very small amount of time and most of the time is spent on the synchronization preparation of the synchronized modifier. Therefore, it is not cost-effective in terms of performance.
Change the code to the following:
Java code
- /**
- * Implement the third attempt to access Kerrigan from a single instance
- */
- Public class singletonkerriganc {
- /**
- * Singleton object instance
- */
- Private Static singletonkerriganc instance = NULL;
- Public static singletonkerriganc getinstance (){
- Synchronized (singletonkerriganc. Class ){
- If (instance = NULL ){
- Instance = new singletonkerriganc ();
- }
- }
- Return instance;
- }
- }
/*** Implement the third attempt to access Kerrigan from a single instance */public class singletonkerriganc {/*** singletonkerriganc instance */Private Static singletonkerriganc instance = NULL; public static singletonkerriganc getinstance () {synchronized (singletonkerriganc. class) {If (instance = NULL) {instance = new singletonkerriganc () ;}} return instance ;}} |
Basically, it does not make sense to move synchronized into the code. Every time getinstance () is called, synchronization is still required. Synchronization itself is fine, but we only want to synchronize it when we create the Kerrigan instance for the first time. Therefore, we have written the following statement: dual lock check (DCL ).
Java code
- /**
- * Implement the fourth attempt to access Kerrigan from a single instance
- */
- Public class singletonkerrigand {
- /**
- * Singleton object instance
- */
- Private Static singletonkerrigand instance = NULL;
- Public static singletonkerrigand getinstance (){
- If (instance = NULL ){
- Synchronized (singletonkerrigand. Class ){
- If (instance = NULL ){
- Instance = new singletonkerrigand ();
- }
- }
- }
- Return instance;
- }
- }
/*** Implement the fourth attempt to access Kerrigan from a single instance */public class singletonkerrigand {/*** singletonkerrigand instance */Private Static singletonkerrigand instance = NULL; public static singletonkerrigand getinstance () {If (instance = NULL) {synchronized (singletonkerrigand. class) {If (instance = NULL) {instance = new singletonkerrigand () ;}} return instance ;}} |
It seems that this has met our requirements. Except for the first object creation, other accesses will be returned in the first if, so they will not go to the synchronization block. Is it perfect?
Let's take a look at this scenario: assuming that the thread executes the sentence "instance = new singletonkerrigand ()", here it looks like a sentence, but in fact it is not an atomic operation (the atomic operation means that this statement is either executed completely or never executed, and half of the statement cannot be executed ). In fact, there are a lot of non-atomic operations in advanced languages. We just need to look at the corresponding assembly code that is compiled and executed in the JVM and find that this sentence is compiled into eight Assembly commands, we did three things:
1. allocate memory to Kerrigan instances.
2. initialize the Kerrigan constructor.
3. Point the instance object to the allocated memory space (note that the instance is not null in this step ).
However, because the Java compiler allows the processor to execute out-of-order, and the cache and register write-back sequence from jmm (Java memory MEDEL) before jdk1.5 to the main memory, the order of the second and third points above cannot be guaranteed. That is to say, the execution order may be 1-2-3-2 or 1-3-2. If it is the latter, in addition, the instance is switched to thread 2 before 3 is finished and 2 is not executed. At this time, because the instance has already executed the third point in thread 1, the instance is no longer empty, therefore, thread 2 directly takes the instance and then uses it, And then reports errors in a logical manner. In addition, it is estimated that debugging may not be able to find such errors that are difficult to trace and reproduce in the last week. This is a coffee cup.
The DCl statement to implement Singleton is recommended in many technical books and textbooks (including books based on previous versions of jdk1.4.), which is actually not completely correct. It is true that DCL is feasible in some languages (such as C), depending on whether two or three steps can be ensured. After jdk1.5, the official team has noticed this problem. Therefore, the jmm has been adjusted and the volatile keyword has been embodied. Therefore, if JDK is a version later than 1.5, you only need to change the instance definition to "Private volatile static singletonkerrigand instance = NULL;" to ensure that every time the instance is read from the main memory, you can use DCL to complete the singleton mode. Of course, volatile will also affect the performance more or less. The most important thing is that we need to consider jdk1.42 and earlier versions. Therefore, the improvement of the single-sample mode writing in this article continues.
The code is getting more and more complex. Now let's look back at the original truth. According to the Rules in JLS (Java language specification), a class will only be initialized once in a classloader, this is ensured by the JVM itself, so we should throw the initialization of the instance to the JVM, and the code will be changed to this:
Java code
- /**
- * Implement the fifth attempt to access Kerrigan from a single instance
- */
- Public class singletonkerrigane {
- /**
- * Singleton object instance
- */
- Private Static singletonkerrigane instance = new singletonkerrigane ();
- Public static singletonkerrigane getinstance (){
- Return instance;
- }
- }
/*** Implement the fifth attempt to access Kerrigan from a single instance */public class singletonkerrigane {/*** singletonkerrigane instance */Private Static singletonkerrigane instance = new singletonkerrigane (); public static singletonkerrigane getinstance () {return instance ;}} |
Well, if this writing method is perfect, the first few paragraphs are the authors entertaining readers. This method does not cause concurrency issues, but it is in the hunger style. After classloader loads the class, the Kerrigan instance will be created immediately. The Hunger style creation method will not be available in some scenarios: for example, if the creation of a Kerrigan instance is dependent on a parameter or configuration file, you must call a method to set the parameter before getinstance (), so that this Singleton method cannot be used.
Let's take a look at the following Singleton method that I think can cope with many scenarios:
Java code
- /**
- * Implement the sixth attempt to access Kerrigan from a single instance
- */
- Public class singletonkerriganf {
- Private Static class singletonholder {
- /**
- * Singleton object instance
- */
- Static final singletonkerriganf instance = new singletonkerriganf ();
- }
- Public static singletonkerriganf getinstance (){
- Return singletonholder. instance;
- }
- }
/*** Implement the sixth attempt to access Kerrigan from a single instance */public class singletonkerriganf {Private Static class singletonholder {/*** single instance object instance */static final singletonkerriganf instance = new singletonkerriganf ();} public static singletonkerriganf getinstance () {return singletonholder. instance ;}} |
This method still uses the JVM mechanism to ensure thread security. Because singletonholder is private, it cannot be accessed except getinstance (), so it is lazy; at the same time, data is not synchronized when the instance is read, and there is no performance defect. It does not depend on the JDK version.
There are many other Singleton modes, such as using a local thread (threadlocal) to process concurrency and ensure the implementation of a single instance in a thread. In the original gof example, the registration method is used to implement the implementation when the single instance class needs to be inherited, and the specified class loader is used to handle multiple implementation in the classloader environment. When we do the development and design work, we shouldWe should not only consider the possible expansion and changes of the demand, but also avoid unnecessary design improvement and implementation complexity caused by the "Phantom demand". In the end, it will lead to loss of construction period, performance and stability. Design insufficiency and excessive design are both harmful. Therefore, there is no best Singleton mode, and only the most appropriate Singleton mode is available.
So far, the singleton mode has come to an end. Finally, we will introduce how to block the construction of Singleton objects in other ways:
1. New singleton object directly
2. Construct a singleton object through reflection
3. Construct a singleton object through serialization.
In the first case, we usually add a private or protected constructor, so that the system will not automatically add the public constructor. Therefore, we can only call the static method in it, you cannot create an object through new.
In the second case, you can use the setaccessible method to break through the private restriction during reflection. While we need to do the first job, we also need to reflectpermission ("suppressaccesschecks ") use the security manager's checkpermission method to limit this breakthrough. In general, the backend configuration is implemented through the application server.
In the third case, if the singleton object needs to implement the serializable interface (rarely), The readresolve () method should be implemented at the same time to ensure that the original object is obtained during deserialization.
Based on the above situation, two methods are added to the singleton mode:
Java code
- /**
- * Single-instance implementation for most cases
- */
- Public class singletonkerrigan implements serializable {
- Private Static class singletonholder {
- /**
- * Singleton object instance
- */
- Static final singletonkerrigan instance = new singletonkerrigan ();
- }
- Public static singletonkerrigan getinstance (){
- Return singletonholder. instance;
- }
- /**
- * The private constructor is used to avoid the use of new to instantiate objects.
- */
- Private singletonkerrigan (){
- }
- /**
- * When the readresolve method is used to serialize a singleton object
- */
- Private object readresolve (){
- Return getinstance ();
- }
- }
/*** Can handle single-instance implementations in most cases */public class singletonkerrigan implements serializable {Private Static class singletonholder {/*** single-instance object instance */static final singletonkerrigan instance = new singletonkerrigan ();} public static singletonkerrigan getinstance () {return singletonholder. instance;}/*** the private constructor is used to avoid the use of new to instantiate the object. */private singletonkerrigan () {}/*** readresolve method to cope with single-instance object serialization */private object readresolve () {return getinstance ();}} |
Summary:
This chapter tries again and again to understand the advantages and disadvantages of various implementation solutions of the singleton mode. I have briefly discussed the double lock detection. I believe everyone can understand from the evolution of various attempts why the singleton mode is the simplest and most complex construction mode.
The constructor modes can be compared with each other, but there are no advantages or disadvantages. Only by determining the context environment can we talk about the application modes. I don't think there is a need to back up some code templates to learn the design patterns. you should understand the causes of each pattern and solve the problems. When you find that your design requires more flexibility, the design will evolve towards the appropriate model. At this time, you will truly master the design model.
[1] For details in this chapter, see when is a singleton not a singleton? In javaworld. And zellux's blogjava blog Singleton mode and dual detection lock (DCL).