I have recently seen some articles on the model implementation, I found that when I realize the Singleton mode, whether it is C , or Java, or Object Pascal, there is no exception to mention "Double Detection Lock", "Name" is not affecting access speed Solved concurrent conflicts, in fact, this method is not reliable.
The principle of "dual detection lock" is roughly, when trying to obtain resources, does not lock operation; if resource allocation is found, enter the mutually exclusive area, then judge once, if it is still not initialized, then initialization operation. Since the modification operation is completed in the mutual exclusion, there will be no two threads (or other operating units) simultaneously modify, avoiding the modified concurrent conflict. The pseudo code is described as follows:
Var _INSTANCE: Instance = nil function get_instance if _instance = nil dam; if _instance = nil dam
The bold character part is a mutually exclusive area, and only the same thread can enter at the same time. The problem is that although there is only one thread to mutually exclusive zone at the same time, it does not guarantee that other threads do not allow other threads to enter non-mutually exclusive. In the mutual exclusive area, if the assignment of _instance is not an atomic operation, it will no longer be reliable in non-mutex.
Why do you say this way? Suppose we work in an ideal concurrent environment, the compiler will not optimize the _instance, nor will it disrupt the code order, and there is no synchronization problem of the cache of the memory, ... Now we simulate two threads. Operation:
Thread a enters GET_INSTANCE
Thread a Remove the value of _instance
Thread a Judgment _instance = nil, result is TRUE
Thread a LOCK;
Thread a Remove the value of _instance
Thread a Judgment _instance = nil, result is TRUE
Thread a call CREATE_INSTANCE, success
Thread a Save the result to _instance, first step
Thread A hangs
Thread B enters GET_INSTANCE
Thread B removes the value of _instance
Thread B judge _instance = nil, result is false (it is very likely, because _instance has changed)
Thread B returns _instance
Thread B uses the return value
Thread B ...
Thread B hangs
Thread a awakening
Thread a Save the results to _Instance, the second step
Thread a unlocked
Thread a ...
Now look at it, what is the result of thread b call GET_INSTANCE? Don't ask me, I really don't know. The key is that the thread A modifies the initial interruption, and there is no to prevent thread B from accessing _instance, resulting in illegal states that _instance in a period of time. If the assignment of _instance is an atomic operation, there is no problem, but many reasons can make it not an atomic operation.
So how do you solve this problem? The above has been mentioned, resulting in two conditions in this question: First, the assignment of _instance is not an atomic operation; the other is that other threads can enter non-interracial when a thread enters the mutex. The solution is naturally any one of these two conditions.
Make the value of _instance becomes a very good choice, but because the type of _instance cannot be arranged casually, it may not be able to implement (but as if JVM guarantees access to 32-bit data is atomic operation, I don't know this guarantee How big is the strength), but we can change a thinking, just not to judge the value of _instance, change to judge a flag that can be assigned with an atomic operation (assuming can use Boolean type): var
_INSTANCE: Instance = NIL
_INITED: BOOLEAN = FALSE
Function Get_instance
IF not _InInited Then
LOCK;
IF not _InInited Then
_INSTANCE = CREATE_INSTANCE
Call atomset (_Ind, true)
END IF
UNLOCK;
END IF
Result = _INSTANCE;
END FUNCTION
Since the assignment of the _Inited flag is atomic operation, it will not be interrupted, so when it is judged in front of the front, _INITED is always valid.
If you can't find this type, there is also a workaround:
VAR
_INSTANCE: Instance = NIL
_INITED: INTEGER = 0
Function Get_instance
if _Inited = 0 Then // If it is not 0, it is interrupted when it is assigned to it, and the value in _Instance is also complete and effective.
LOCK;
IF _INSTANCE = NIL THEN / / Due to the mutual exclusive area, there will be no other thread hang in the mutex, this judgment is reliable.
_INSTANCE = CREATE_INSTANCE
_INITED = 1
END IF
UNLOCK;
END IF
Result = _INSTANCE;
END FUNCTION
In this way, if a thread is interrupted by the action of _instance assignment, _INITED = 0 judgment is true, the current thread will call the Lock to block; if a thread is interrupted by _Inited assignment operation, then then _ It is already a legitimate valid value in Instance, and there will be no problem no matter whether it _Inited = 0 is established.
Talking about the method of assigning a atomic operation, and another method is of course to make other threads cannot access the _instance when a thread handles the mutex. At the same time, we must allow all threads to access _instance at the same time without threads, if you have some experience in concurrency control, you can think that - completely correct, using multi_read_exclusive_write's lock mechanism. This will ensure that there is no other thread to read _instance when a thread modifies _instance. But when you implement it, you should also be kill, you can't:
VAR
_INSTANCE: Instance = NIL
Function Get_instance
Begin_read
IF not _InInited Then
Begin_write
IF not _InInited Then
_INSTANCE = CREATE_INSTANCE
Call atomset (_Ind, true)
END IF
END_WRITE
END IF
Result = _INSTANCE;
END_READ
End function If this is implemented, it is possible to perform begin_read, then thread a execution begin_write, blocked-wait thread B execute end_read; thread B will also perform begin_write, also blocked - Wait a thread A to execute End_Write ......
The correct implementation is this:
VAR
_INSTANCE: Instance = NIL
Function Get_instance
Begin_read
IF not _InInited Then
END_READ
Begin_write
IF not _InInited Then
_INSTANCE = CREATE_INSTANCE
Call atomset (_Ind, true)
END IF
END_WRITE
Begin_read
END IF
Result = _INSTANCE;
END_READ
END FUNCTION