Revise volatile access to singletonInstance field

For defensiveness against a singletonInstance/initialized visibility mismatch, we accept the locking overhead for pre-initialized null values (where we need the initialized field) in favor of a defensive fast path for non-null values (where we only need the singletonInstance field).

Closes gh-35905
This commit is contained in:
Juergen Hoeller
2025-11-26 17:20:11 +01:00
parent 45d4fd3b7e
commit a15274d431
@@ -47,12 +47,13 @@ public class SingletonSupplier<T extends @Nullable Object> implements Supplier<T
private volatile @Nullable T singletonInstance;
private volatile boolean initialized;
private boolean initialized;
/**
* Guards access to write operations on the {@code singletonInstance} field.
* Guards access to write operations on the {@code singletonInstance} and
* {@code initialized} fields.
*/
private final Lock writeLock = new ReentrantLock();
private final Lock initializationLock = new ReentrantLock();
/**
@@ -99,8 +100,12 @@ public class SingletonSupplier<T extends @Nullable Object> implements Supplier<T
@Override
public @Nullable T get() {
T instance = this.singletonInstance;
if (!this.initialized) {
this.writeLock.lock();
if (instance == null) {
// Either not initialized yet, or a pre-initialized null value ->
// specific determination follows within full initialization lock.
// Pre-initialized null values are rare, so we accept the locking
// overhead in favor of a defensive fast path for non-null values.
this.initializationLock.lock();
try {
instance = this.singletonInstance;
if (!this.initialized) {
@@ -115,7 +120,7 @@ public class SingletonSupplier<T extends @Nullable Object> implements Supplier<T
}
}
finally {
this.writeLock.unlock();
this.initializationLock.unlock();
}
}
return instance;