You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring启动时主线程锁对象致Hazelcast线程阻塞问题求助

Spring集成Hazelcast启动死锁问题(Spring 4.1.4+)

问题概述

在Spring 4.1.4及后续版本(最高可升级至4.3.30.RELEASE)中集成Hazelcast 3.11时,应用启动阶段出现死锁:

  • 主线程初始化Holder Bean时,通过CachedRepository调用Hazelcast Map的get方法,等待Hazelcast线程返回数据
  • Hazelcast线程执行MapLoader.load方法时,Spring事务切面尝试获取DefaultSingletonBeanRegistry的锁,而该锁已被主线程持有,导致双方阻塞

该问题在Spring 4.1.3.RELEASE中不存在,推测由Spring 4.1.4的内部逻辑变更导致。

相关配置与代码

Spring XML配置

<bean id="holder" class="org.example.api.Holder">
    <constructor-arg>
        <bean factory-bean="cachedRepository" factory-method="getAllValueIds">
            <constructor-arg>
                <util:constant static-field="org.example.api.domain.MyStaticField.SOME_FIELD"/>
            </constructor-arg>
        </bean>
    </constructor-arg>
</bean>

<bean id="cachedRepository" class="org.example.api.repository.CachedRepository">
    <property name="byIdCache">
        <bean factory-bean="hazelcastInstance" factory-method="getMap">
            <constructor-arg value="byId"/>
        </bean>
    </property>
</bean>

CachedRepository核心代码

public List<Integer> getAllValueIds(SomeField someField) {
    return byIdCache.get(someField);
}

死锁线程分析

主线程状态(启动线程)

主线程持有DefaultSingletonBeanRegistry的锁(对应对象0x00000006ca38d540),并等待Hazelcast的InvocationFuture返回结果:

"org.example.Launcher.main()" #25 prio=5 os_prio=0 tid=0x000001b6ef442800 nid=0x578 waiting on condition [0x0000000b5f0f9000]
   java.lang.Thread.State: WAITING (parking)
    ...
    at com.hazelcast.map.impl.proxy.MapProxyImpl.get(MapProxyImpl.java:120)
    at org.example.api.repository.CachedRepository.getAllValueIds(CachedRepository.java:43)
    at org.example.api.Holder.init(Holder.java:51)
    ...
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
    - locked <0x00000006ca38d540> (a java.util.concurrent.ConcurrentHashMap)

Hazelcast线程状态

Hazelcast的分区操作线程尝试获取上述锁,陷入阻塞,原因是事务切面在查找TransactionManager时触发了BeanFactory的类型检查,而该检查需要获取SingletonBeanRegistry的锁:

"hz.application.partition-operation.thread-4" #89 prio=5 os_prio=0 tid=0x000001b6ed53e000 nid=0x32a4 waiting for monitor entry [0x0000000b637fe000]
   java.lang.Thread.State: BLOCKED (on object monitor)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:188)
    - waiting to lock <0x00000006ca38d540> (a java.util.concurrent.ConcurrentHashMap)
    ...
    at org.springframework.transaction.interceptor.TransactionAspectSupport.determineQualifiedTransactionManager(TransactionAspectSupport.java:396)
    ...
    at org.example.api.cache.MapLoader$$EnhancerBySpringCGLIB$$d59bbb21.load(<generated>)
    at com.hazelcast.map.impl.MapStoreWrapper.load(MapStoreWrapper.java:165)
    ...

解决方案(适配Spring 4.1.4~4.3.30)

方案1:延迟Holder Bean的初始化逻辑

避免在Spring上下文初始化阶段调用Hazelcast分布式操作,改为在上下文刷新完成后执行初始化:

  1. 修改Holder类,移除构造方法中的初始化逻辑,新增init方法
  2. 实现ApplicationListener<ContextRefreshedEvent>,在事件触发时调用Holder.init()
public class Holder implements ApplicationListener<ContextRefreshedEvent> {
    private List<Integer> valueIds;
    private CachedRepository cachedRepository;

    // 注入cachedRepository,而非通过构造参数直接获取数据
    public void setCachedRepository(CachedRepository cachedRepository) {
        this.cachedRepository = cachedRepository;
    }

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        this.valueIds = cachedRepository.getAllValueIds(MyStaticField.SOME_FIELD);
    }
}

同时修改XML配置,移除构造参数,改为注入cachedRepository:

<bean id="holder" class="org.example.api.Holder">
    <property name="cachedRepository" ref="cachedRepository"/>
</bean>

方案2:调整MapLoader的事务传播属性

如果MapLoader.load方法不需要事务,或可以使用非强制事务的传播策略,避免触发TransactionManager的查找逻辑:
在MapLoader的load方法上添加事务注解,指定传播属性为SUPPORTS或NOT_SUPPORTED:

@Transactional(propagation = Propagation.NOT_SUPPORTED)
public Object load(Object key) {
    // 原有加载逻辑
}

这样事务切面不会尝试查找TransactionManager,也就不会触发BeanFactory的锁竞争。

方案3:确保依赖Bean提前初始化

通过depends-on属性,让Holder Bean依赖于MapLoader和TransactionManager相关Bean,确保这些Bean在Holder之前完全初始化:

<bean id="holder" class="org.example.api.Holder" depends-on="mapLoader,transactionManager">
    <constructor-arg>
        <bean factory-bean="cachedRepository" factory-method="getAllValueIds">
            <constructor-arg>
                <util:constant static-field="org.example.api.domain.MyStaticField.SOME_FIELD"/>
            </constructor-arg>
        </bean>
    </constructor-arg>
</bean>

注意替换mapLoader和transactionManager为实际的Bean ID。

方案4:懒加载Holder的业务数据

将Holder中的数据获取逻辑改为懒加载,仅在第一次使用时调用Hazelcast:

public class Holder {
    private volatile List<Integer> valueIds;
    private CachedRepository cachedRepository;

    public Holder(CachedRepository cachedRepository) {
        this.cachedRepository = cachedRepository;
    }

    public List<Integer> getValueIds() {
        if (valueIds == null) {
            synchronized (this) {
                if (valueIds == null) {
                    valueIds = cachedRepository.getAllValueIds(MyStaticField.SOME_FIELD);
                }
            }
        }
        return valueIds;
    }
}

同时修改XML配置,注入cachedRepository而非直接获取数据:

<bean id="holder" class="org.example.api.Holder">
    <constructor-arg ref="cachedRepository"/>
</bean>

内容的提问来源于stack exchange,提问作者wmel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 06:05:24