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

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

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

Loading…
Cancel
Save