Threads in Spring

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

posted @ 2013-08-04 18:53  gofblogs  阅读(1070)  评论(0编辑  收藏  举报