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
- Declare the shared field
volatilein Java or@Volatilein Kotlin. - Prefer an initialization-on-demand holder, an enum singleton or a dependency injection container where practical.
- 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.