与DBCP连接池和线程混淆

时间:2014-08-07 00:15:38

标签: java multithreading spring hibernate apache-commons-dbcp

我正在尝试在我的Java Web应用程序中使用多线程,似乎无论我尝试什么,我遇到了一些连接池问题。

我目前的流程是我循环遍历所有部门并处理它们,最终生成显示。这需要时间,所以我想为每个部门生成一个线程并让它们同时处理。

经过很多时间首先弄清楚如何让我的hibernate会话在一个线程中保持打开以防止延迟初始化加载错误,我终于有了创建我的Thread类的Spring bean的解决方案,并创建了一个新的每个线程的bean的实例。我试过了2个不同的版本

1)我直接将DAO类注入Bean。失败 - 加载页面几次后,我会得到“无法获得连接,池错误等待空闲对象超时”,每个线程都会崩溃。

2)好的,然后我尝试将spring SessionFactory注入到我的bean中,然后创建我的DAO的新实例并使用SessionFactory进行设置。我的DAO对象都扩展为HibernateDaoSupport。失败 - 加载页面几次后,我会收到“太多连接”错误消息。

现在我感到困惑的是我的SessionFactory bean是一个单例,我理解这意味着它是一个在整个Spring容器中共享的单个Object。如果这是真的,那么为什么每个Thread在创建新连接时应该只是共享该单个实例?看来我所有的连接池都被填满了,我不明白为什么。一旦线程完成,所有创建的连接都应该被释放但是没有。我甚至尝试对注入的SessionFactory运行close()操作,但这没有效果。我尝试限制在5处同时运行多少个线程,希望这会导致一次创建没有那么多连接,但没有运气。

我显然做错了什么,但我不确定是什么。我是否完全采取了错误的方法试图让我的Session进入我的Thread?我不知道怎么不正确管理我的连接池?任何想法将不胜感激!

更多信息我想到:我的进程总共创建了大约25个线程,每次运行5个线程。在我开始收到错误之前,我能够刷新我的页面大约3次。显然,每次刷新都会产生并保持一堆连接。

这是我的spring配置文件:

<bean id="mainDataSource" class="org.apache.commons.dbcp.BasicDataSource"
    destroy-method="close">

....

<!-- Purposely put big values in here trying to figure this issue out 
since my standard smaller values didn't help.  These big values Fail just the same -->

    <property name="maxActive"><value>500</value></property>
    <property name="maxIdle"><value>500</value></property>
    <property name="minIdle"><value>500</value></property>
    <property name="maxWait"><value>5000</value></property>
    <property name="removeAbandoned"><value>true</value></property>
    <property name="validationQuery"><value>select 0</value></property>
</bean>

<!--Failed attempt #1 -->
<bean id="threadBean" class="controller.manager.ManageApprovedFunds$TestThread" scope="prototype">
        <property name="dao"><ref bean="dao" /></property>
    </bean>

<!--Failed attempt #2 -->
<bean id="threadBean" class="manager.ManageApprovedFunds$TestThread" scope="prototype">
        <property name="sessionFactory"><ref bean="sessionFactory" /></property>
    </bean>

Java代码:

ExecutorService taskExecutor = Executors.newFixedThreadPool(5);

for(Department dept : getDepartments()) {
    TestThread t = (TestThread)springContext.getBean("threadBean");
    t.init(dept, form, getSelectedYear());
    taskExecutor.submit(t);
}

taskExecutor.shutdown();

public static class TestThread extends HibernateDaoSupport implements Runnable, ApplicationContextAware {
        private ApplicationContext appContext;

        @Override
        public void setApplicationContext(ApplicationContext arg0)
                throws BeansException {
            this.appContext = arg0;
        }

        @Override
        public void run() {
            try {
                MyDAO dao = new MyDAO();
                dao.setSessionFactory(getSessionFactory());

                //SOME READ OPERATIONS

                getSessionFactory().close();
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }       
    }

2 个答案:

答案 0 :(得分:1)

基本上你不应该尝试在线程之间共享一个hibernate Session。 Hibernate会话和实体对象都是线程安全的,这可能会导致一些令人惊讶的问题。 Here和[here] 3 []是一个小而精彩的读物,评论中也有一些价值。基本上不要尝试在线程之间共享Session

还有另一个问题,因为整个事务管理基于ThreadLocal个对象,获取当前会话和底层JDBC连接也是如此。现在尝试生成线程将导致令人惊讶的问题,其中之一将是连接池饥饿。 (注意:不要打开太多连接,更多信息here)。

除了不打开多个连接之外,您应该知道启动多个线程。线程绑定到cpu(如果有多个核心,则绑定到核心)。添加到许多线程可能导致在许多线程之间大量共享单个cpu /核心。这可能会破坏您的性能而不是增加它。

简而言之,恕我直言,你的方法是错误的,每个线程应该只是阅读它关心的实体,做它的事情并提交交易。我建议使用像Spring Batch这样的东西,而不是发明自己的机制。 (虽然如果它很简单,我可能会选择它。)

答案 1 :(得分:1)

数据库连接与SessionFactory无关,但与Session无关。为了使您的处理正确,您必须注意以下事项:

  • 只创建一个SessionFactory实例(每个持久化上下文) - 但您已经这样做了
  • 不要close() SessionFactory - 它的生命周期应该在应用程序终止时结束 - 即处于取消部署或服务器关闭状态。
  • 请务必始终close() Session - 每个开放Session使用一个数据库连接,如果这些连接没有关闭,则表示您正在泄漏连接。