使用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。由代码可以看到,无论是直接获取还是新创建,操作过程都是线程安全的。