最近啓動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();
}
}
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>