diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java index 433e42b15271..e5b789543cf9 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java @@ -199,6 +199,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto /** Whether bean definition metadata may be cached for all beans. */ private volatile boolean configurationFrozen; + private volatile boolean preInstantiationPhase; + private final NamedThreadLocal preInstantiationThread = new NamedThreadLocal<>("Pre-instantiation thread marker"); @@ -1001,8 +1003,9 @@ protected void checkMergedBeanDefinition(RootBeanDefinition mbd, String beanName } @Override - protected boolean isCurrentThreadAllowedToHoldSingletonLock() { - return (this.preInstantiationThread.get() != PreInstantiation.BACKGROUND); + @Nullable + protected Boolean isCurrentThreadAllowedToHoldSingletonLock() { + return (this.preInstantiationPhase ? this.preInstantiationThread.get() != PreInstantiation.BACKGROUND : null); } @Override @@ -1017,6 +1020,8 @@ public void preInstantiateSingletons() throws BeansException { // Trigger initialization of all non-lazy singleton beans... List> futures = new ArrayList<>(); + + this.preInstantiationPhase = true; this.preInstantiationThread.set(PreInstantiation.MAIN); try { for (String beanName : beanNames) { @@ -1031,7 +1036,9 @@ public void preInstantiateSingletons() throws BeansException { } finally { this.preInstantiationThread.remove(); + this.preInstantiationPhase = false; } + if (!futures.isEmpty()) { try { CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java index 146b31b93acf..c9d443973779 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java @@ -99,9 +99,6 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements /** Names of beans currently excluded from in creation checks. */ private final Set inCreationCheckExclusions = ConcurrentHashMap.newKeySet(16); - @Nullable - private volatile Thread singletonCreationThread; - /** Flag that indicates whether we're currently within destroySingletons. */ private volatile boolean singletonsCurrentlyInDestruction = false; @@ -242,37 +239,33 @@ protected Object getSingleton(String beanName, boolean allowEarlyReference) { public Object getSingleton(String beanName, ObjectFactory singletonFactory) { Assert.notNull(beanName, "Bean name must not be null"); - boolean acquireLock = isCurrentThreadAllowedToHoldSingletonLock(); + Boolean lockFlag = isCurrentThreadAllowedToHoldSingletonLock(); + boolean acquireLock = !Boolean.FALSE.equals(lockFlag); boolean locked = (acquireLock && this.singletonLock.tryLock()); try { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { - if (acquireLock) { - if (locked) { - this.singletonCreationThread = Thread.currentThread(); + if (acquireLock && !locked) { + if (Boolean.TRUE.equals(lockFlag)) { + // Another thread is busy in a singleton factory callback, potentially blocked. + // Fallback as of 6.2: process given singleton bean outside of singleton lock. + // Thread-safe exposure is still guaranteed, there is just a risk of collisions + // when triggering creation of other beans as dependencies of the current bean. + if (logger.isInfoEnabled()) { + logger.info("Creating singleton bean '" + beanName + "' in thread \"" + + Thread.currentThread().getName() + "\" while other thread holds " + + "singleton lock for other beans " + this.singletonsCurrentlyInCreation); + } } else { - Thread threadWithLock = this.singletonCreationThread; - if (threadWithLock != null) { - // Another thread is busy in a singleton factory callback, potentially blocked. - // Fallback as of 6.2: process given singleton bean outside of singleton lock. - // Thread-safe exposure is still guaranteed, there is just a risk of collisions - // when triggering creation of other beans as dependencies of the current bean. - if (logger.isInfoEnabled()) { - logger.info("Creating singleton bean '" + beanName + "' in thread \"" + - Thread.currentThread().getName() + "\" while thread \"" + threadWithLock.getName() + - "\" holds singleton lock for other beans " + this.singletonsCurrentlyInCreation); - } - } - else { - // Singleton lock currently held by some other registration method -> wait. - this.singletonLock.lock(); - locked = true; - // Singleton object might have possibly appeared in the meantime. - singletonObject = this.singletonObjects.get(beanName); - if (singletonObject != null) { - return singletonObject; - } + // No specific locking indication (outside a coordinated bootstrap) and + // singleton lock currently held by some other creation method -> wait. + this.singletonLock.lock(); + locked = true; + // Singleton object might have possibly appeared in the meantime. + singletonObject = this.singletonObjects.get(beanName); + if (singletonObject != null) { + return singletonObject; } } } @@ -291,7 +284,6 @@ public Object getSingleton(String beanName, ObjectFactory singletonFactory) { if (recordSuppressedExceptions) { this.suppressedExceptions = new LinkedHashSet<>(); } - this.singletonCreationThread = Thread.currentThread(); try { singletonObject = singletonFactory.getObject(); newSingleton = true; @@ -313,7 +305,6 @@ public Object getSingleton(String beanName, ObjectFactory singletonFactory) { throw ex; } finally { - this.singletonCreationThread = null; if (recordSuppressedExceptions) { this.suppressedExceptions = null; } @@ -336,10 +327,15 @@ public Object getSingleton(String beanName, ObjectFactory singletonFactory) { * Determine whether the current thread is allowed to hold the singleton lock. *

By default, any thread may acquire and hold the singleton lock, except * background threads from {@link DefaultListableBeanFactory#setBootstrapExecutor}. + * @return {@code false} if the current thread is explicitly not allowed to hold + * the lock, {@code true} if it is explicitly allowed to hold the lock but also + * accepts lenient fallback behavior, or {@code null} if there is no specific + * indication (traditional behavior: always holding a full lock) * @since 6.2 */ - protected boolean isCurrentThreadAllowedToHoldSingletonLock() { - return true; + @Nullable + protected Boolean isCurrentThreadAllowedToHoldSingletonLock() { + return null; } /** diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/BeanFactoryLockingTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/BeanFactoryLockingTests.java index 519dac59fb23..d7c3291d5d53 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/BeanFactoryLockingTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/BeanFactoryLockingTests.java @@ -38,7 +38,7 @@ void fallbackForThreadDuringInitialization() { new RootBeanDefinition(ThreadDuringInitialization.class)); beanFactory.registerBeanDefinition("bean2", new RootBeanDefinition(TestBean.class, () -> new TestBean("tb"))); - beanFactory.getBean(ThreadDuringInitialization.class); + beanFactory.preInstantiateSingletons(); }