@@ -133,6 +133,11 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
133
133
* System property that instructs Spring to enforce strict locking during bean creation,
134
134
* rather than the mix of strict and lenient locking that 6.2 applies by default. Setting
135
135
* this flag to "true" restores 6.1.x style locking in the entire pre-instantiation phase.
136
+ * <p>By default, the factory infers strict locking from the encountered thread names:
137
+ * If additional threads have names that match the thread prefix of the main bootstrap thread,
138
+ * they are considered external (multiple external bootstrap threads calling into the factory)
139
+ * and therefore have strict locking applied to them. This inference can be turned off through
140
+ * explicitly setting this flag to "false" rather than leaving it unspecified.
136
141
* @since 6.2.6
137
142
* @see #preInstantiateSingletons()
138
143
*/
@@ -156,8 +161,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
156
161
private static final Map <String , Reference <DefaultListableBeanFactory >> serializableFactories =
157
162
new ConcurrentHashMap <>(8 );
158
163
159
- /** Whether lenient locking is allowed in this factory. */
160
- private final boolean lenientLockingAllowed = ! SpringProperties .getFlag (STRICT_LOCKING_PROPERTY_NAME );
164
+ /** Whether strict locking is enforced or relaxed in this factory. */
165
+ private @ Nullable final Boolean strictLocking = SpringProperties .checkFlag (STRICT_LOCKING_PROPERTY_NAME );
161
166
162
167
/** Optional id for this factory, for serialization purposes. */
163
168
private @ Nullable String serializationId ;
@@ -208,6 +213,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
208
213
209
214
private volatile boolean preInstantiationPhase ;
210
215
216
+ private @ Nullable volatile String mainThreadPrefix ;
217
+
211
218
private final NamedThreadLocal <PreInstantiation > preInstantiationThread =
212
219
new NamedThreadLocal <>("Pre-instantiation thread marker" );
213
220
@@ -1030,7 +1037,7 @@ protected void checkMergedBeanDefinition(RootBeanDefinition mbd, String beanName
1030
1037
}
1031
1038
}
1032
1039
else {
1033
- // Bean intended to be initialized in main bootstrap thread
1040
+ // Bean intended to be initialized in main bootstrap thread.
1034
1041
if (this .preInstantiationThread .get () == PreInstantiation .BACKGROUND ) {
1035
1042
throw new BeanCurrentlyInCreationException (beanName , "Bean marked for mainline initialization " +
1036
1043
"but requested in background thread - enforce early instantiation in mainline thread " +
@@ -1041,8 +1048,28 @@ protected void checkMergedBeanDefinition(RootBeanDefinition mbd, String beanName
1041
1048
1042
1049
@ Override
1043
1050
protected @ Nullable Boolean isCurrentThreadAllowedToHoldSingletonLock () {
1044
- return (this .lenientLockingAllowed && this .preInstantiationPhase ?
1045
- this .preInstantiationThread .get () != PreInstantiation .BACKGROUND : null );
1051
+ if (this .preInstantiationPhase ) {
1052
+ // We only differentiate in the preInstantiateSingletons phase.
1053
+ PreInstantiation preInstantiation = this .preInstantiationThread .get ();
1054
+ if (preInstantiation != null ) {
1055
+ // A Spring-managed thread:
1056
+ // MAIN is allowed to lock (true) or even forced to lock (null),
1057
+ // BACKGROUND is never allowed to lock (false).
1058
+ return switch (preInstantiation ) {
1059
+ case MAIN -> (Boolean .TRUE .equals (this .strictLocking ) ? null : true );
1060
+ case BACKGROUND -> false ;
1061
+ };
1062
+ }
1063
+ if (Boolean .FALSE .equals (this .strictLocking ) ||
1064
+ (this .strictLocking == null && !getThreadNamePrefix ().equals (this .mainThreadPrefix ))) {
1065
+ // An unmanaged thread (assumed to be application-internal) with lenient locking,
1066
+ // and not part of the same thread pool that provided the main bootstrap thread
1067
+ // (excluding scenarios where we are hit by multiple external bootstrap threads).
1068
+ return true ;
1069
+ }
1070
+ }
1071
+ // Traditional behavior: forced to always hold a full lock.
1072
+ return null ;
1046
1073
}
1047
1074
1048
1075
@ Override
@@ -1060,6 +1087,7 @@ public void preInstantiateSingletons() throws BeansException {
1060
1087
1061
1088
this .preInstantiationPhase = true ;
1062
1089
this .preInstantiationThread .set (PreInstantiation .MAIN );
1090
+ this .mainThreadPrefix = getThreadNamePrefix ();
1063
1091
try {
1064
1092
for (String beanName : beanNames ) {
1065
1093
RootBeanDefinition mbd = getMergedLocalBeanDefinition (beanName );
@@ -1072,6 +1100,7 @@ public void preInstantiateSingletons() throws BeansException {
1072
1100
}
1073
1101
}
1074
1102
finally {
1103
+ this .mainThreadPrefix = null ;
1075
1104
this .preInstantiationThread .remove ();
1076
1105
this .preInstantiationPhase = false ;
1077
1106
}
@@ -1166,6 +1195,12 @@ private void instantiateSingleton(String beanName) {
1166
1195
}
1167
1196
}
1168
1197
1198
+ private static String getThreadNamePrefix () {
1199
+ String name = Thread .currentThread ().getName ();
1200
+ int numberSeparator = name .lastIndexOf ('-' );
1201
+ return (numberSeparator >= 0 ? name .substring (0 , numberSeparator ) : name );
1202
+ }
1203
+
1169
1204
1170
1205
//---------------------------------------------------------------------
1171
1206
// Implementation of BeanDefinitionRegistry interface
0 commit comments