1

I have been reading the Wikipedia article on double check locking and one thing that's not clear from it (at least to me) is what the pattern achieves. What is the reason for making the two checks below? Why not have triple checked locking? Wouldn't that be even better?

if (mySingleton == null) { // 1st check
            lock (myLock) {
                if (mySingleton == null) { // 2nd (double) check
                    mySingleton = new MySingleton();
                }
            }
        }
8
  • 5
    Read the first sentence of the Wikipedia article: "used to reduce the overhead of acquiring a lock by first testing the locking criterion (the "lock hint") without actually acquiring the lock". The point is to save locking effort in the common case that the initialization is not necessary. Sep 16, 2013 at 10:42
  • possible duplicate of Is the singleton pattern prone to thread safety problems?
    – gnat
    Sep 16, 2013 at 10:48
  • 1
    The question is about double-check locking; the example is a singleton pattern. This is not a duplicate. Apr 9 at 2:50
  • 1
    @Basilevs there's no language tagged, but the use of volatile you mention is only correct in Java. In C and C++ it works on some platforms as an extension, but is not portable.
    – Useless
    Apr 10 at 9:53
  • 1
    volatile isn't deprecated in C++20, but it has never been portably usable for synchronization. Using volatile to emit fences is an extension specific to MS (and maybe some other) platforms. The only standard mechanisms are std::atomic since C++11 and _Atomic since C11.
    – Useless
    Apr 10 at 15:34

3 Answers 3

15

The correct version without double check locking is:

lock (myLock) {
    if (mySingleton == null) { // 2nd (double) check
        mySingleton = new MySingleton();
    }
}

However this requires the expensive operation of getting a lock each time you want to get the singleton, however the lock is actually only need once (during the first initialization).

So the double check was introduce where a thread will check mySingleton outside the lock and if it passes the first check (mySingleton is not null) then you don't need to bother with acquiring the lock at all.

The inner check remains to avoid the race condition when 2 threads reach the lock block to initialize mySingleton however this can cause a double initialization if the inner check is not there (2 threads go in the lock block on at the time).

Adding a third check is useless, there are only 2 real states here: outside the lock and inside the lock. Where would you put the third check?

2
  • 1
    Are you sure locking is expensive like you claim? Before you optimize you must measure. The DCL pattern reeks of premature optimization or data gathered 20 years ago on a different language. Testing and acquiring uncontested locks is usually virtually free. If there is heavy contention for a lock on the singleton, there are likely better approaches to the problem as a whole.
    – Nick
    Apr 24, 2019 at 20:46
  • 1
    @Nick Accessing a singleton may happen an awful lot. Like in a tight loop. Like you have an array that is a singleton and someone writes “for (I = 0; I < singletonArray().count; ++i) singletonArray()[i] = 0;
    – gnasher729
    Apr 9 at 7:11
1

Locking is, was and always will be expensive because if you didn't get blocked, someone else is. Generally it is best to use libraries that encapsulate thread-sync in more efficient ways, using locks as a last resort.

But to answer the question as asked:

Think of a microphone being passed around an audience for people to ask questions. You don't request the microphone before you know you have a question to ask - that would be the first check. Then when you get the microphone, you have to recall the question and determine if it is still relevant.

Technically you could just request the mic and wait till you get it to decide if you have anything to say - and that would be very rude, annoying and while maybe you didn't notice a difference in waiting time - everyone after you does.

1
  • 2
    "Locking is, was and always will be expensive because if you didn't get blocked, someone else is." this is only true if the threads are requesting the lock at the same time. Acquiring a lock in e.g. Java is actually quite fast. You've got the right idea about why locks can be expensive in a high-contention situation but it's not always the case. In fact, if there's low-contention, a lock-based approach can be much more performant than a lock-free one and it's often easier to reason about.
    – JimmyJames
    Apr 9 at 16:45
0

Its worth noting that in C# there is a thread safe approach that avoids the need to use locking for creating the singleton instance.

class MyClass
{
    //  Static constructor - Can only by called once per application domain.
    static MyClass()
    {
        //  We must be the only thread.
        MyClass.Instance = new MyClass(); 
    }

    static public MyClass Instance { get; private set; }

    protected MyClass()
    {
        //  Class constructor.
    }
}
10
  • 1
    -1, because the code is particularly incomplete and makes no sense as is. Where's the MyClass.Instance you assign in the static constructor? Where is this.Instance initialization? Sep 16, 2013 at 11:30
  • The Instance property was in the original code, although has been corrected to have a static declaration on it. Sep 16, 2013 at 13:12
  • That said, being down voted because of a minor typo in the code seems a bit harsh. Especially as the key point to the post is very valid, that you can safely initialise C# singletons without the need for locking. Sep 16, 2013 at 13:15
  • 2
    Got it. Still maintaining the -1, given the weirdness of the solution. See here, the fourth version, which is close to yours, but guarantees that the instance will always stay the same and will never be null. Sep 16, 2013 at 13:34
  • 3
    This only works for singletons, which should be avoided anyways. For other locking sort of examples, this trick is not available. (and it doesn't answer the question)
    – Telastyn
    Sep 16, 2013 at 13:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.