我试图让多线程在我的Javaweb应用程序中工作,似乎无论我尝试什么,我都遇到了连池的一些问题。
我目前的流程是循环遍历我所有的部门并处理它们,最终生成一个显示。这需要时间,所以我想为每个部门生成一个线程并让它们并发处理。
在第一次弄清楚如何让我的Hibernate会话在线程中保持打开状态以防止延迟初始化加载错误之后,我终于找到了创建Thread类的Spring bean的解决方案,并为每个线程创建该bean的新实例。我尝试了2个不同的版本
1)我直接将DAO类注入Bean。失败-加载页面几次后,每个线程都会收到“无法获得连接,池错误等待空闲对象超时”,应用程序会崩溃。
2)好的,然后我尝试将SpringSessionFactory
注入我的bean,然后创建我的DAO的新实例并使用SessionFactory
设置它。我的DAO对象都扩展了HibernateDaoSupport
。失败-加载页面几次后,我会收到“连接过多”的错误消息。
现在我感到困惑的是,我的SessionFactory
bean是一个单例,我理解这意味着它是一个在整个Spring容器中共享的单个对象。如果这是真的,那么为什么它看起来像每个线程都在创建一个新连接,而它应该只共享那个单一实例?似乎我所有的连接池都被填满了,我不明白为什么。一旦线程完成,所有创建的连接都应该被释放,但没有。我甚至尝试对注入的SessionFactory
运行关闭()操作,但没有效果。我试图限制5个线程同时运行的数量,希望这不会导致一次创建这么多连接,但运气不好。
我显然做错了什么,但我不确定是什么。我在试图让我的HibernateSession
进入我的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();
}
}
}
基本上,您不应该尝试在线程之间共享单个hibernateSession
。Hibernate会话和实体对象都是线程安全的,这可能会导致一些令人惊讶的问题。这里和[这里] 3[]是一个小而好的阅读,注释中也有一些价值。基本上不要尝试在线程之间共享Session
。
还有另一个问题,因为整个事务管理是基于ThreadLocal
对象的,获取当前会话和底层JDBC连接也是如此。现在尝试生成线程会导致令人惊讶的问题,其中之一是连接池饥饿。(注意:不要打开太多连接,更多信息在这里)。
除了不要打开太多连接之外,您还应该注意开始使用多个线程。一个线程绑定到一个cpu(如果您有多个内核,则绑定到内核)。添加到多个线程可能会导致多个线程之间大量共享单个cpu/内核。这会扼杀您的性能而不是提高性能。
简而言之IMHO你的方法是错误的,每个线程应该简单地读取它关心的实体,做它的事情并提交事务。我建议使用像Spring Batch这样的东西来实现这一点,而不是发明你自己的机制。(尽管如果它足够简单,我可能会去做)。
数据库连接与SessionFactory
无关,但与Session
有关。要正确处理,您必须注意以下事项:
SessionFactory
实例(每个持久性上下文)-但您已经在这样做了关闭()
SessionFactory
-它的生命周期应该在应用程序死亡时结束-即在取消部署或服务器关闭时。close()
您的Session
-您每次打开Session
都会使用一个数据库连接,如果这些连接没有关闭,您就会泄漏连接。