Skip to content

Commit a044357

Browse files
committed
Apply lenient locking fallback to singleton pre-instantiation phase only
Closes gh-33463
1 parent 1ff2678 commit a044357

File tree

3 files changed

+38
-35
lines changed

3 files changed

+38
-35
lines changed

spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
199199
/** Whether bean definition metadata may be cached for all beans. */
200200
private volatile boolean configurationFrozen;
201201

202+
private volatile boolean preInstantiationPhase;
203+
202204
private final NamedThreadLocal<PreInstantiation> preInstantiationThread =
203205
new NamedThreadLocal<>("Pre-instantiation thread marker");
204206

@@ -1001,8 +1003,9 @@ protected void checkMergedBeanDefinition(RootBeanDefinition mbd, String beanName
10011003
}
10021004

10031005
@Override
1004-
protected boolean isCurrentThreadAllowedToHoldSingletonLock() {
1005-
return (this.preInstantiationThread.get() != PreInstantiation.BACKGROUND);
1006+
@Nullable
1007+
protected Boolean isCurrentThreadAllowedToHoldSingletonLock() {
1008+
return (this.preInstantiationPhase ? this.preInstantiationThread.get() != PreInstantiation.BACKGROUND : null);
10061009
}
10071010

10081011
@Override
@@ -1017,6 +1020,8 @@ public void preInstantiateSingletons() throws BeansException {
10171020

10181021
// Trigger initialization of all non-lazy singleton beans...
10191022
List<CompletableFuture<?>> futures = new ArrayList<>();
1023+
1024+
this.preInstantiationPhase = true;
10201025
this.preInstantiationThread.set(PreInstantiation.MAIN);
10211026
try {
10221027
for (String beanName : beanNames) {
@@ -1031,7 +1036,9 @@ public void preInstantiateSingletons() throws BeansException {
10311036
}
10321037
finally {
10331038
this.preInstantiationThread.remove();
1039+
this.preInstantiationPhase = false;
10341040
}
1041+
10351042
if (!futures.isEmpty()) {
10361043
try {
10371044
CompletableFuture.allOf(futures.toArray(new CompletableFuture<?>[0])).join();

spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java

Lines changed: 28 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,6 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
9999
/** Names of beans currently excluded from in creation checks. */
100100
private final Set<String> inCreationCheckExclusions = ConcurrentHashMap.newKeySet(16);
101101

102-
@Nullable
103-
private volatile Thread singletonCreationThread;
104-
105102
/** Flag that indicates whether we're currently within destroySingletons. */
106103
private volatile boolean singletonsCurrentlyInDestruction = false;
107104

@@ -242,37 +239,33 @@ protected Object getSingleton(String beanName, boolean allowEarlyReference) {
242239
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
243240
Assert.notNull(beanName, "Bean name must not be null");
244241

245-
boolean acquireLock = isCurrentThreadAllowedToHoldSingletonLock();
242+
Boolean lockFlag = isCurrentThreadAllowedToHoldSingletonLock();
243+
boolean acquireLock = !Boolean.FALSE.equals(lockFlag);
246244
boolean locked = (acquireLock && this.singletonLock.tryLock());
247245
try {
248246
Object singletonObject = this.singletonObjects.get(beanName);
249247
if (singletonObject == null) {
250-
if (acquireLock) {
251-
if (locked) {
252-
this.singletonCreationThread = Thread.currentThread();
248+
if (acquireLock && !locked) {
249+
if (Boolean.TRUE.equals(lockFlag)) {
250+
// Another thread is busy in a singleton factory callback, potentially blocked.
251+
// Fallback as of 6.2: process given singleton bean outside of singleton lock.
252+
// Thread-safe exposure is still guaranteed, there is just a risk of collisions
253+
// when triggering creation of other beans as dependencies of the current bean.
254+
if (logger.isInfoEnabled()) {
255+
logger.info("Creating singleton bean '" + beanName + "' in thread \"" +
256+
Thread.currentThread().getName() + "\" while other thread holds " +
257+
"singleton lock for other beans " + this.singletonsCurrentlyInCreation);
258+
}
253259
}
254260
else {
255-
Thread threadWithLock = this.singletonCreationThread;
256-
if (threadWithLock != null) {
257-
// Another thread is busy in a singleton factory callback, potentially blocked.
258-
// Fallback as of 6.2: process given singleton bean outside of singleton lock.
259-
// Thread-safe exposure is still guaranteed, there is just a risk of collisions
260-
// when triggering creation of other beans as dependencies of the current bean.
261-
if (logger.isInfoEnabled()) {
262-
logger.info("Creating singleton bean '" + beanName + "' in thread \"" +
263-
Thread.currentThread().getName() + "\" while thread \"" + threadWithLock.getName() +
264-
"\" holds singleton lock for other beans " + this.singletonsCurrentlyInCreation);
265-
}
266-
}
267-
else {
268-
// Singleton lock currently held by some other registration method -> wait.
269-
this.singletonLock.lock();
270-
locked = true;
271-
// Singleton object might have possibly appeared in the meantime.
272-
singletonObject = this.singletonObjects.get(beanName);
273-
if (singletonObject != null) {
274-
return singletonObject;
275-
}
261+
// No specific locking indication (outside a coordinated bootstrap) and
262+
// singleton lock currently held by some other creation method -> wait.
263+
this.singletonLock.lock();
264+
locked = true;
265+
// Singleton object might have possibly appeared in the meantime.
266+
singletonObject = this.singletonObjects.get(beanName);
267+
if (singletonObject != null) {
268+
return singletonObject;
276269
}
277270
}
278271
}
@@ -291,7 +284,6 @@ public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
291284
if (recordSuppressedExceptions) {
292285
this.suppressedExceptions = new LinkedHashSet<>();
293286
}
294-
this.singletonCreationThread = Thread.currentThread();
295287
try {
296288
singletonObject = singletonFactory.getObject();
297289
newSingleton = true;
@@ -313,7 +305,6 @@ public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
313305
throw ex;
314306
}
315307
finally {
316-
this.singletonCreationThread = null;
317308
if (recordSuppressedExceptions) {
318309
this.suppressedExceptions = null;
319310
}
@@ -336,10 +327,15 @@ public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
336327
* Determine whether the current thread is allowed to hold the singleton lock.
337328
* <p>By default, any thread may acquire and hold the singleton lock, except
338329
* background threads from {@link DefaultListableBeanFactory#setBootstrapExecutor}.
330+
* @return {@code false} if the current thread is explicitly not allowed to hold
331+
* the lock, {@code true} if it is explicitly allowed to hold the lock but also
332+
* accepts lenient fallback behavior, or {@code null} if there is no specific
333+
* indication (traditional behavior: always holding a full lock)
339334
* @since 6.2
340335
*/
341-
protected boolean isCurrentThreadAllowedToHoldSingletonLock() {
342-
return true;
336+
@Nullable
337+
protected Boolean isCurrentThreadAllowedToHoldSingletonLock() {
338+
return null;
343339
}
344340

345341
/**

spring-beans/src/test/java/org/springframework/beans/factory/BeanFactoryLockingTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ void fallbackForThreadDuringInitialization() {
3838
new RootBeanDefinition(ThreadDuringInitialization.class));
3939
beanFactory.registerBeanDefinition("bean2",
4040
new RootBeanDefinition(TestBean.class, () -> new TestBean("tb")));
41-
beanFactory.getBean(ThreadDuringInitialization.class);
41+
beanFactory.preInstantiateSingletons();
4242
}
4343

4444

0 commit comments

Comments
 (0)