使用Spring时经常会问,我们定义的Bean应该是Singleton还是Prototype?多个客户端同时调用Dao层,需要考虑线程安全吗?通过阅读官方文档和Spring的源代码,这类问题的答案是:自定义的Stateless Bean是不需要考虑线程安全问题的,可以在配置时设置为Singleton,减少new操作,提高程序效率;自定义的Stateful Bean是需要考虑线程安全问题的,Spring没有提供任何安全机制,只能由开发人员自己处理,比如使用ThreadLocal的方式或在配置时设置为Prototype。对于Dao层,Spring框架做了特殊处理。DataSource声明为Singleton模式,但其中的Connections是由支持线程安全的集合保存。与此同时,EntityManagerFactory是线程安全的,EntityManager不是,所以在每个Dao函数中都会首先执行:EntityManager entityManager = entityManagerFactory.createEntityManager()。如果追溯到最基本的JdbcTemplate用法,其官网的实例代码是:
public class JdbcCorporateEventDao implements CorporateEventDao { private JdbcTemplate jdbcTemplate; public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
}
这表明每一个客户端调用都会产生一个新的JdbcTemplate对象,所以JdbcCorporateEventDao也就不会出现线程安全问题了。下面的代码是一个自定义的有状态Bean。程序运行结果表明,两个线程都在改变它的状态(成员变量),并且这种影响是不确定的。
package com.mmh.main; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext; import com.mmh.printer.PrintHelper; public class Application { public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(
"appContext.xml"); ExecutorService executors = Executors.newCachedThreadPool(); executors.execute(new PrintThread(context));
executors.execute(new PrintThread(context));
}
} class PrintThread implements Runnable { private ApplicationContext context; public PrintThread(ApplicationContext context) {
this.context = context;
} public void run() {
PrintHelper printHelper = context.getBean(PrintHelper.class); try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
} for (int i = 0; i < 2; i++) {
printHelper.print();
printHelper.increamentYears();
}
}
}
// 输出结果:
/*
* Thread[pool-1-thread-1,5,main]: yzp---28
* Thread[pool-1-thread-2,5,main]: yzp---28
* Thread[pool-1-thread-2,5,main]: yzp---30
* Thread[pool-1-thread-1,5,main]: yzp---29
*/
把自定义Bean的scope设定为:prototype,程序运行结果表明,它的状态没有被两个线程相互干扰。
<?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="printHelper" class="com.mmh.printer.PrintHelper" scope="prototype">
<constructor-arg index="0" value="yzp"/>
<constructor-arg index="1" value="28"/>
</bean> </beans>
// 输出结果:
/*
* Thread[pool-1-thread-1,5,main]: yzp---28
* Thread[pool-1-thread-1,5,main]: yzp---29
* Thread[pool-1-thread-2,5,main]: yzp---28
* Thread[pool-1-thread-2,5,main]: yzp---29
*/
在debug时跟踪代码可以看到,IoC容器初始化时会创建一个注册类DefaultSingletonBeanRegistry,在这个类里有这样一行代码:private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64),其中的singletonObjects就是保存SingletonBean的集合,它的类型是ConcurrentHashMap,该集合本身支持线程安全。下面的代码是Spring的源代码,getBean()获取SingletonBean的核心内容。
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
如果singletonObjects中有需要的Bean,就直接取出来返回,如果没有,那么在同步块中使用ObjectFactory创建一个新的Bean。由代码可以看到,无论是直接获取还是新创建,操作过程都是线程安全的。