Unsafe double-checked locking

Unsafe double-checked locking

Description

Lazy initialization with double-checked locking can let another thread observe an incompletely initialized object if the shared field is not volatile. This can cause unpredictable behavior for objects whose consistency matters, such as security configuration, authentication state and singleton caches.

Potential impact

  • Use of security state before initialization is complete
  • Unstable authentication or authorization decisions
  • Intermittent failures and concurrency errors that are difficult to reproduce

Remediation

  1. Declare the shared field volatile in Java or @Volatile in Kotlin.
  2. Prefer an initialization-on-demand holder, an enum singleton or a dependency injection container where practical.
  3. Make security-state objects immutable.

Examples

Before

java
private static Config instance;

public static Config getInstance() {
    if (instance == null) {
        synchronized (Config.class) {
            if (instance == null) {
                instance = new Config();
            }
        }
    }
    return instance;
}

After

java
private static volatile Config instance;

Explanation:

  • Before: Initializes the object with double-checked locking but without volatile, so another thread may observe it before initialization is fully visible.
  • After: Declares the same shared field volatile. The rest of the accessor remains as shown above.

References