mutated × shared — the answer changes between runs
Lazy-Init Visibility Race (Double-Checked Locking)
A thread sees the singleton's reference published before its fields are -- so it reads a half-built object, but only on the rare interleaving.
01the recipe
In the wild
compound ofImproper InitializationCWE-543 Unsynced SingletonThreading & MutexesCWE-667compoundCWE-609 Double-Checked Locking
example.java
// SMELL: double-checked locking without volatile.
// (improper initialization x threading & mutexes)
static Config inst; // not volatile
static Config get() {
if (inst == null) { // 1st check, no lock
synchronized (Config.class) {
if (inst == null)
inst = new Config(); // the ref can publish before the fields
}
}
return inst; // another thread may see a half-built Config
}
// RIGHT: volatile makes the publication safe (or use a holder class / enum).
static volatile Config inst;
static Config get() {
Config c = inst;
if (c == null) {
synchronized (Config.class) {
c = inst;
if (c == null) inst = c = new Config();
}
}
return c;
}Without volatile, the constructor's writes and the reference assignment can be reordered, so a second thread reading inst can see a non-null reference to an object whose fields are still defaults. It only fires on the exact interleaving + visibility window, so it survives every single-threaded test. volatile (or an initialization-on-demand holder) makes the publication safe.
// observed
broken: rare reads of a half-initialized singleton; vanishes under a debugger right: safe publication -- every reader sees a fully-built Config
02weakness catalog
Mapped weaknesses (CWE)
On its own, this defect is catalogued by MITRE as one or more of these weaknesses. The exploitable vulnerability usually appears only when it chains or combines with another.