Learn the vocabulary of checking initialization twice around a lock to skip its cost once already initialized.
0 / 5 completed
1 / 5
At standup, a dev mentions a pattern that checks whether a shared resource is already initialized once without acquiring any lock, and only if it isn't, acquires the lock and checks again before actually initializing it, specifically to avoid paying the lock's cost on every single call. What is this pattern called?
Double-checked locking is exactly this pattern: it checks whether a shared resource is already initialized once, without acquiring any lock, and only if that first check says it isn't, acquires the lock and checks a second time, since another thread might have finished initializing it in the meantime, before actually performing the initialization. A hash collision is an unrelated hash-table concept about two keys sharing a bucket. This check-then-lock-then-check-again structure is exactly what lets a lazily-initialized shared resource skip the lock's cost entirely on every call after the first, once initialization has already completed.
2 / 5
During a design review, the team is warned that double-checked locking is unsafe on some platforms and languages unless the initialized flag or reference is marked appropriately, for instance as volatile, or unless the language's memory model otherwise guarantees the ordering. Which risk does this warning address?
This warning addresses another thread observing the initialization flag as set before the object it points to is actually fully constructed and visible, since without an appropriate ordering guarantee, like marking the flag or reference volatile or otherwise relying on the language's memory model, the CPU or compiler can reorder the flag's write ahead of the object's construction becoming visible, letting a second thread's fast, lock-free check see a flag that says "ready" while the object behind it is still only partially built. This is exactly the subtle, famously broken failure mode double-checked locking is notorious for on platforms and languages that don't provide the right memory-ordering guarantee by default.
3 / 5
In a code review, a dev notices a double-checked-locking implementation for a lazily-initialized singleton uses a plain, non-volatile reference for the singleton instance, on a platform known to allow memory reordering. What does this represent?
This is a classic double-checked-locking bug, since using a plain, non-volatile reference on a platform known to allow memory reordering risks another thread's fast, lock-free check observing that reference as non-null before the singleton object it points to has actually finished being constructed and made fully visible. A cache eviction policy is an unrelated concept about discarded cache entries. This exact bug is precisely why double-checked locking earned its reputation as deceptively easy to get subtly wrong, and why the appropriate ordering guarantee, like marking the reference volatile, is essential rather than optional.
4 / 5
An incident report shows a lazily-initialized singleton occasionally handed out a reference to a partially-constructed object under concurrent access, because its double-checked-locking implementation used a plain, non-volatile reference on a platform that permits memory reordering. What practice would prevent this?
Marking the singleton's reference appropriately, such as volatile, provides the ordering guarantee that ensures the referenced object is fully constructed and visible to other threads before that reference can ever be observed as non-null, which is exactly the fix for the partially-constructed reference described in this incident. Continuing to use a plain, non-volatile reference regardless of the platform's memory-reordering behavior is exactly what allowed the singleton to occasionally hand out a reference to an incompletely built object. This ordering-guarantee fix is the standard, well-documented remedy for making double-checked locking actually safe rather than just appearing to work most of the time.
5 / 5
During a PR review, a teammate asks why the team still bothers with double-checked locking's extra unlocked-then-locked-then-checked-again structure instead of just always acquiring the lock on every single call to the lazily-initialized accessor. What is the reasoning?
Always acquiring the lock on every single call to the accessor would pay the lock's cost forever, even for the overwhelming majority of calls that happen long after initialization has already completed, while double-checked locking's fast, unlocked first check lets every one of those later calls skip the lock entirely, only falling back to acquiring it during the brief window before initialization finishes. The tradeoff is that this optimization only works correctly with the right memory-ordering guarantee in place, since without it the pattern is exactly the kind of subtly broken code that appears to work in testing but fails intermittently under real concurrent load. This is precisely why the extra structure is worth the added care, once implemented correctly.