Browse Source

Apply lenient locking fallback to singleton pre-instantiation phase only

Closes gh-33463
pull/33525/head
Juergen Hoeller 2 years ago
parent
commit
a044357c31
  1. 11
      spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java
  2. 60
      spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java
  3. 2
      spring-beans/src/test/java/org/springframework/beans/factory/BeanFactoryLockingTests.java

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

@ -199,6 +199,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto @@ -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<PreInstantiation> preInstantiationThread =
new NamedThreadLocal<>("Pre-instantiation thread marker");
@ -1001,8 +1003,9 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto @@ -1001,8 +1003,9 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
}
@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 class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto @@ -1017,6 +1020,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
// Trigger initialization of all non-lazy singleton beans...
List<CompletableFuture<?>> futures = new ArrayList<>();
this.preInstantiationPhase = true;
this.preInstantiationThread.set(PreInstantiation.MAIN);
try {
for (String beanName : beanNames) {
@ -1031,7 +1036,9 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto @@ -1031,7 +1036,9 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
}
finally {
this.preInstantiationThread.remove();
this.preInstantiationPhase = false;
}
if (!futures.isEmpty()) {
try {
CompletableFuture.allOf(futures.toArray(new CompletableFuture<?>[0])).join();

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

@ -99,9 +99,6 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements @@ -99,9 +99,6 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
/** Names of beans currently excluded from in creation checks. */
private final Set<String> 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 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements @@ -242,37 +239,33 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
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 class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements @@ -291,7 +284,6 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
if (recordSuppressedExceptions) {
this.suppressedExceptions = new LinkedHashSet<>();
}
this.singletonCreationThread = Thread.currentThread();
try {
singletonObject = singletonFactory.getObject();
newSingleton = true;
@ -313,7 +305,6 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements @@ -313,7 +305,6 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
throw ex;
}
finally {
this.singletonCreationThread = null;
if (recordSuppressedExceptions) {
this.suppressedExceptions = null;
}
@ -336,10 +327,15 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements @@ -336,10 +327,15 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
* Determine whether the current thread is allowed to hold the singleton lock.
* <p>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;
}
/**

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

@ -38,7 +38,7 @@ class BeanFactoryLockingTests { @@ -38,7 +38,7 @@ class BeanFactoryLockingTests {
new RootBeanDefinition(ThreadDuringInitialization.class));
beanFactory.registerBeanDefinition("bean2",
new RootBeanDefinition(TestBean.class, () -> new TestBean("tb")));
beanFactory.getBean(ThreadDuringInitialization.class);
beanFactory.preInstantiateSingletons();
}

Loading…
Cancel
Save