1. 程式人生 > >spring容器初始化時候遇到的死鎖問題

spring容器初始化時候遇到的死鎖問題

最近啟動spring專案的時候遇到一個死鎖問題,使用jstack獲取執行緒堆疊的時候,可以看到2個執行緒出現了死鎖:


DefaultSingletonBeanRegistry.getSingleton()原始碼如下,可以看到這個方法需要對singletonObjects加鎖


第二處xxx.subject.core.cache.DataLocalcacheInit.afterPropertiesSet原始碼如下:


可以看到:這個bean在初始化的時候,會開啟執行緒,呼叫另外一個bean的initData()方法從資料庫載入資料。等資料載入完畢,DataLocalcacheInit這個bean的初始化才算完成。

通過上面的堆疊可以看出:spring容器在初始化bean的時候,會對singletonObjects物件加鎖;我們自己在afterPropertiesSet()方法中開啟了一個執行緒,最終也會觸發spring載入另外的bean。第一個執行緒(初始化spring的main執行緒)還沒有釋放鎖,第二個執行緒(自己開啟的執行緒),也需要獲取singletonObjects物件鎖,這樣就出現了死鎖。表現出來的現象就是:spring容器卡在那裡,不能完成所有bean的初始化。

來看一段例子,這個例子和我們專案中實際程式碼很相似。FirstBean呼叫ConfigHelper中的方法:

public class FirstBean implements InitializingBean {

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("first bean is initializing....");

        BlockingQueue queue = new ArrayBlockingQueue(10);
        Thread thread = new Thread() {

            @Override
            public void run() {
                ConfigHelper.doSomething();
                queue.add(1);
            }
        };

        thread.start();

        queue.take();
        System.out.println("first get data....");

    }
}

ConfigHelper程式碼如下:通過BeanFactory獲取到另外一個bean
public class ConfigHelper implements BeanFactoryAware {
    private static BeanFactory factory;

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.factory = beanFactory;
    }

    public static void doSomething()
    {
        SecondBean bean = (SecondBean)factory.getBean("second");
        bean.say();
    }
}
SecondBean程式碼很簡單如下:
public class SecondBean {

    public void say() {
        System.out.println("SecondBean....");
    }
}


spring配置檔案和啟動程式碼如下,執行可以發現出現死鎖:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">


    <bean id="config" class="net.aty.spring.deadlock.ConfigHelper"></bean>
    <bean id="first" class="net.aty.spring.deadlock.FirstBean"></bean>
    <bean id="second" class="net.aty.spring.deadlock.SecondBean"></bean>

</beans>
public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new FileSystemXmlApplicationContext(
                "src/main/java/net/aty/spring/deadlock/deadlock.xml");// 載入 spring 配置檔案

    }
}


spring初始化的時候,如果我們在spring提供的一些擴充套件點處(BeanFactoryAware/InitializingBean等),開啟執行緒去獲取bean,很容器出現死鎖。因為spring初始化單例bean(大多數bean都是單例的)會加鎖。如果初始化1個bean的時候,還沒有釋放鎖,另一個執行緒再次觸發spring載入bean,就會出現死鎖。

解決上面的問題很簡單:FirstBean邏輯上是依賴於ConfigHelper和SecondBean的,但是我們卻並沒有顯示地告訴spring這種邏輯關係。spring初始化FirstBean的時候,進入afterPropertiesSet(),這個方法開啟了執行緒會觸發另外2個bean的載入。我們只要顯示地告訴spring這種依賴關係,讓spring先載入ConfigHelper和SecondBean就可以了。

<bean id="config" class="net.aty.spring.deadlock.ConfigHelper" depends-on="second"></bean>
<bean id="first" class="net.aty.spring.deadlock.FirstBean" depends-on="config"></bean>
<bean id="second" class="net.aty.spring.deadlock.SecondBean"></bean>