This article introduces the code mode called double-check locking (DCL) mode. It works in singleton mode and multiton mode) and discusses the DCl mode in Visual Basic. net and C. The source code of Visual Basic. Net can be seen in the text. The source code of C # is provided in the appendix. This document assumes that the reader is familiar with the multi-threading concepts of Visual Basic. Net or C #, the basic concepts of design patterns, and the basic UML icons. The DCl pattern (double-check locking pattern) is also known as the double-check pattern. It is only useful in multi-threaded environments. It is transplanted from the C language. In C language, the DCl mode is often used in late instantiation of classes in multi-threaded environments. The DCl mode is usually used together with the factory mode to reuse product objects cyclically. If the reader is familiar with the singleton mode, the DCl mode can be used in the "lazy" Singleton mode to provide unique product objects. Through further promotion, we can use the multiton and flyweight modes. Starting from the factory Mode To explain what the DCl mode is, let's start with the factory mode. In the following class diagram, the factory class factory0 has a sharing method getinstance () used to provide the product class product instance.
Figure 1. A system composed of the factory class and product class. |
The source code of factory0 is as follows:
Public class factory0 Public shared function getinstance () as product Return New Product () End Function End Class |
Code List 1. source code of the factory0 class |
Obviously, you only need to call the getinstance () method to obtain the product class instance, and each call gets a new instance. The product class provides a counting method. You can call the getcount () method to obtain the total number of all instances of the product.
Public class product Private shared count as integer = 0Public sub new () Count + = 1 System. Console. writeline ("product number {0} is created.", count) End sub Public shared function getcount () as integer Return count End Function End Class |
Code List 2. Source Code of product products |
However, if the product instance must be used cyclically, but cannot be created without limit, the content of the factory method getinstance () must be rewritten to implement the necessary cyclic logic. The simplest cyclic logic is to reuse a single product instance. For example, the following source code implements the logic of a single product instance:
Public class factory1 Private shared instance as productPublic shared function getinstance () as product If (instance is nothing) then Instance = new product () End if Return instance End Function End Class |
Code List 3. Source Code of factory factory1 |
Isn't it easy anymore? If you have already created a product-type instance, the instance will be returned. Otherwise, you will first create the instance, record it, and then return it. To write such code, it is obvious that there is only one product instance in the system; therefore, if (instance is nothing) then check will be performed. Obviously, if the code is run in a multi-threaded environment, two or more product objects will be created in the above Code, resulting in errors. In a multi-threaded environment, if two threads a and B almost reach the IF (instance is nothing) Then Statement at the same time, assuming that thread a is a little earlier than thread B, then: 1. A will first enter the IF (instance is nothing) then block and start executing the new product () statement. At this time, the instance variable is still nothing until the new product () Statement of thread a returns and assigns a value to the instance variable. 2. however, thread B does not wait outside the IF (instance is nothing) then statement, because at this time the instance is nothing and it will immediately enter if (instance is nothing) inside the then block. In this way, thread B will inevitably execute the statement of instance = new product () to create a second instance. 3. After the execution of the Instance = new product () Statement of thread a is completed, the instance variable gets a real object reference and the instance is no longer true. The third thread will not be in the IF (instance is nothing) then statement block. 4. Then, the instance = new product () Statement of thread B is executed, and the value of the instance variable is overwritten. However, the fact that the first product object is referenced by thread A does not change. At this time, thread a and thread B each have an independent product object, which is wrong. To intuitively view the execution result of the program, run the following client code:
Private sub run1 () Dim o as product O = factory1.getinstance System. Console. writeline ("Total number of objects created: {0}", O. getcount) End subPrivate sub btncreatemediaclick (...) Handles btncreate1.click Dim T (9) as thread Dim count as integer For Count = 0 to 9 T (count) = new thread (addressof run1) T (count). Start () Next End sub |
Code list 4. source code of the Client |
In addition, add the following to the first line of the getinstance () method of factory1:
Statement, equivalent to simulating a lengthy product creation process, so that the first thread to enter is waiting for the following thread, thus highlighting the problem of multithreading. The above client code uses 10 threads to call the factory method at the same time, and then calls the product counting method to print out the total number of instances of the product class. If you run the code, you will find that the factory method will create more than one product instance. When I run this code, the system generates nine product instances. Therefore, factory1 fails in a multi-threaded environment as a factory that uses the product instance cyclically. If you use a client similar to the Code in Listing 4, you can see that the system has created only one product instance from start to end. A thread-safe version To overcome the disadvantages of no thread security, the following provides a thread-safe getinstance () method:
<Methodimpl (methodimploptions. Synchronized)> _ Public shared function getinstance () as product Thread. Sleep (10)If (instance is nothing) then Instance = new product () End if Return instance End Function |
Code List 5. This is the correct answer to thread security. |
Obviously, because the entire static factory method is synchronized, there will be no two threads entering this method at the same time. Therefore, when thread a and thread B call this method simultaneously or almost simultaneously as the first batch of callers: When thread a arrives a little earlier, it will first enter this method, and thread B will wait outside the method; 1. For thread A, the value of the instance variable is nothing, so the instance = new product () statement will be executed. 2. Thread a stops executing the method. The value of the instance variable is no longer nothing. 3. Thread B enters this method. The value of the instance variable is no longer nothing, so the instance = new product () statement will not be executed. Thread B obtains the reference contained in the instance variable, that is, the reference to the product instance created by thread. Obviously, it is correct that thread a and thread B hold the same product instance. After reading this article, you can refer to questions 1, 2, and 3. Optimized thread security version-DCL Mode
Before proceeding to the discussion in this Section, first review the mutex class. Mutex can provide exclusive access restrictions to achieve synchronization by allowing only one thread to access this resource. To obtain access permission, you must call the waitone () method. If no other thread is currently accessed, the thread can obtain access permission; otherwise, the thread will wait at this statement. When the access ends, you can call the releasemutex () method to release the access permission. After carefully reviewing the code listing 5 above, we will find that synchronization is only useful before the instance variable is assigned a value for the first time. After the instance variable has a value, synchronization actually becomes an unnecessary bottleneck. If there is a way to remove this small extra overhead, isn't it more perfect? Therefore, we have the following cleverly designed double-check locking ).
Public class factory3 Private shared instance as product Private shared M as mutex = new mutex ()Private sub new () System. Console. writeline ("factory object is created .") End sub Public shared function getinstance () as product Thread. Sleep (10) If (instance is nothing) then'' location 1 ''Location 2 M. waitone () ''Location 3 If (instance is nothing) then'' location 4 Instance = new product () End if M. releasemutex () End if Return instance End Function End Class |
Code List 6: lazy factory classes using DCL |
For the readers who first came into contact with the DCl model, the idea of this technique is not obviously easy to understand. Therefore, this article provides a detailed explanation here. Similarly, we assume that thread a and thread B, as the first batch of callers, call static factory methods at the same time or almost simultaneously. 1. Because threads A and B are the first callers, the instance variable is nothing when they enter this static factory method. Therefore, thread a and thread B arrive at location 1 at the same time or almost simultaneously. 2. Assume that thread a will first reach Location 2, enter M. waitone (), and reach location 3. At this time, due to the synchronization restrictions of M. waitone (), thread B cannot reach the position 3, but can only wait at the position 2. 3. Thread a executes the instance = new product () statement to obtain a value for the instance variable, that is, a reference to a product object. At this time, thread B can only wait at location 2. 4. Thread a exits M. waitone (), returns the instance object, and exits the static factory method. 5. Thread B enters the M. waitone () block, reaches position 3, and then reaches position 4. Because the instance variable is no longer a nothing, thread B exits M. waitone (): return the product object referenced by the instance (that is, the product object created by thread a) and exit the static factory method. So far, thread a and thread B have the same product object. As you can see, in the above method getinstance (), synchronization is only used to prevent multiple threads from initializing this class at the same time, rather than calling this static factory method at the same time. If this is correct, then the "lazy" factory class can get rid of the synchronization bottleneck and reach a perfect realm. This is the DCl mode. Here, you can see if you can answer questions 4, 5, and 6 after this article. DCL Mode Readers who first came into contact with this technique will certainly have many problems, such as the first or second check, which may be saved. The answer is: according to the multi-thread principle and the DCl model, they cannot be saved. First, if the first check is omitted, the factory method will become as follows:
Public shared function getinstance () as product Thread. Sleep (10) ''Location 1 ''Location 2 M. waitone () ''Location 3 If (instance is nothing) then'' location 4 Instance = new product () End if M. releasemutex ()Return instance End Function |
Code List 7. The factory method for thread security of the first check is omitted |
This causes the product instance to wait at location 2, whether or not it exists, that is, the factory method that is equal to the thread security before optimization (see Code List 5 ), although there is no error in generating more than one product object, it does not achieve the goal of optimization. Second, if the second check is omitted, the factory method mode will become as follows:
If (instance is nothing) then'' location 1 ''Location 2 M. waitone () ''Location 3 Instance = new product () M. releasemutex () End if Return instance |
Code List 8: The Factory method for thread security that skips the second check |
Can this be done? It is also assumed that thread a and thread B, as the first batch of callers, call static factory methods at the same time or almost simultaneously. 1. Because threads A and B are the first callers, the instance variable is nothing when they enter this static factory method. Therefore, thread a and thread B arrive at location 1 at the same time or almost simultaneously. 2. Assume that thread a will first reach Location 2 and enter M. waitone () to reach location 3. At this time, due to the synchronization restrictions of M. waitone (), thread B cannot reach the position 3, but can only wait at the position 2. 3. Thread a executes the instance = new product () statement to obtain a value for the instance variable, that is, a reference to a product object. At this time, thread B can only wait at location 2. 4. Thread a exits M. waitone (), returns the instance object, and exits the static factory method. 5. thread B enters M. waitone () block, reaching position 3. Thread B executes the instance = new product () statement to get a new value for the instance variable and B exits the static factory method. Therefore, threads a and B create two product class instances. In other words, it is not possible to have a second check. Application of DCL mode in singleton Mode
The Singleton mode describes only one instance class, which is called the singleton class. The Singleton class provides its own unique instance to the outside world. Generally, Singleton mode is mostly used in multi-threaded environments, which makes thread synchronization very important. According to the different creation methods of Singleton instances, the implementation of Singleton mode can be divided into two types: "Hungry" and "lazy ". "Lazy" Singleton mode determines whether to create a product instance when the factory method is called: If the instance already exists, return the instance directly. Otherwise, create an instance first, store the instance and return it to the instance. Readers familiar with the singleton mode should be aware that the DCl mode can be used in the singleton mode of "lazy. In fact, the singleton class is a special case of the DCl mode. You only need to merge the factory class and product class to get the singleton class. See the following UML class diagram.
Figure 2. A singleton class |
The source code of this Singleton class is as follows:
Public class Singleton Private shared instance as Singleton Private shared M as mutex = new mutex ()Public sub new () System. Console. writeline ("singleton object is created .") End sub Public shared function getinstance () as Singleton Thread. Sleep (10) If instance is nothing then M. waitone () If instance is nothing then Instance = new Singleton () End if M. releasemutex () End if Return instance End Function End Class |
Code List 9. Singleton class with double check thread security |
DCL model promotion The implementation of the DCl mode described above is based on the simplest logic, that is, the single instance logic. This logic can be further extended into a more general circular logic. For example, a factory object can control the maximum number of product instances. If the maximum value is 1, it becomes the logic of a single instance. If the value is greater than 1, it becomes the logic of multiple instances. If the product object is stateful, although the factory object does not control the number of product instances, it cyclically uses product instances according to the product object status, for example, only one (or N) Product instances in each status are allowed. Q &
1st. Use mutex to rewrite Code List 5. 2nd. Use monitor to rewrite Code List 5. 3rd. Use synclock to rewrite Code List 5. 4th. Use monitor to rewrite code list 6. 5th. Use synclock to rewrite code list 6. 6th. Use monitor to rewrite Code List 9. 7th. Use synclock to rewrite Code List 9. Q & A answers 1st. The result of Listing 5 is as follows:
Public class factory2a Private shared instance as product Private shared M as mutex = new mutex ()Private sub new () System. Console. writeline ("factory object is created .") End sub Public shared function getinstance () as product Thread. Sleep (10) M. waitone () If (instance is nothing) then Instance = new product () End if M. releasemutex () Return instance End Function End Class |
Code List 10. Singleton class for thread-safe double check |
2nd answer: the monitor object provides a synchronization lock for a resource object. Use the monitor object to rewrite Code List 5. The result is:
Public class factory2b Private shared instance as productPrivate sub new () System. Console. writeline ("factory object is created .") End sub Public shared function getinstance () as product Thread. Sleep (10) Monitor. Enter (GetType (factory2b )) If (instance is nothing) then Instance = new product () End if Monitor. Exit (GetType (factory2b )) Return instance End Function End Class |
Code List 11. Singleton class for thread-safe double check |
3rd answer: The synclock version is as follows:
Public class factory2c Private shared instance as productPrivate sub new () System. Console. writeline ("factory object is created .") End sub Public shared function getinstance () as product Thread. Sleep (10) Synclock (GetType (factory2c )) If (instance is nothing) then Instance = new product () End if End synclock Return instance End Function End Class |
Code List 12. Singleton class for thread-safe double check |
4th answer: the factory class of the double check lock after the monitor object is rewritten is:
Public class factory3a Private shared instance as productPublic shared function getinstance () as product Thread. Sleep (10) If (instance is nothing) then Monitor. Enter (GetType (factory3a )) If (instance is nothing) then Instance = new product () End if Monitor. Exit (GetType (factory3a )) End if Return instance End Function End Class |
Code List 13. Singleton class for thread-safe double check |
5th answer: the dual-check lock factory class after synclock is rewritten is;
Public class factory3b Private shared instance as productPublic shared function getinstance () as product Thread. Sleep (10) If (instance is nothing) then Synclock (GetType (factory3b )) If (instance is nothing) then Instance = new product () End if End synclock End if Return instance End Function End Class |
Code list 14. Singleton class for thread-safe double check |
6th the source code for rewriting the singleton mode with the monitor object is as follows:
Public class singletona Private shared instance as singletonaPublic sub new () System. Console. writeline ("singleton object is created .") End sub Public shared function getinstance () as singletona Thread. Sleep (10) If instance is nothing then Monitor. Enter (GetType (singletona )) If instance is nothing then Instance = new singletona () End if Monitor. Exit (GetType (singletona )) End if Return instance End Function End Class |
Code List 15. Singleton class for thread-safe double check |
7th the source code of the singleton mode after synclock is used is as follows:
Public class singletonb Private shared instance as singletonbPublic sub new () System. Console. writeline ("singleton object is created .") End sub Public shared function getinstance () as singletonb Thread. Sleep (10) If instance is nothing then Synclock (GetType (singletonb )) If instance is nothing then Instance = new singletonb () End if End synclock End if Return instance End Function End Class |
Code List 16. Singleton class for thread-safe double check |