开拓线程安详的Spring Web应用
副标题#e#
媒介
假如开拓者正开拓或维护基于Servlet的Web应用,则Servlet类型发起最好可以或许看看。因为它含有的内容对付Web应用开拓者领略Servlet容器的事情机理很有辅佐。
个中,类型给出了Servlet容器是如那里理惩罚客户请求的。Servlet容器将会按照web.xml设置文件中界说的各个Servet而建设相应的单例。因此,多个客户请求大概同时会见这些单例,即多个线程同时会见它们。在Web应用中担保线程安详是很重要的。开拓者应该对这个问题保持鉴戒,并且必需确保各自的代码必需以线程安详的方法运行。
复习线程安详
大部门Java开拓者都应该听过synchronized要害字。在不回收任何第三方库的前提下,Java自己对线程提供了原生支持,并且synchronized要害字往往是Java应用中实现线程安详最重要的因素。Java中的同步提供了互斥支持。通过同步一块代码或整个要领可以或许担保同时最多只有单个线程执行它,从而实现了线程安详。引入同步具有副浸染,即阻塞。好比,大公司或状师办公室的前台小姐同时需要处理惩罚电话、邮件、受访客户等等。这使得她的事情很忙碌,并且导致一些工作不可以或许实时处理惩罚。
在Web应用中需要鉴戒阻塞。受同步掩护的代码块使得其同时处理惩罚客户请求的吞吐量低落,并且许多客户处于阻塞状态,除非某客户处理惩罚完成。并且互斥不只会带来阻塞,还会带来死锁。凡是,死锁是不行规复的。如下条件将触发死锁的产生:线程A锁住了线程B期待的资源,并且线程B锁住了线程A期待的资源,即线程B一直在期待线程A释放锁,线程A也是如此。因此,对付多线程的应用而言,死锁的防范和处理惩罚凡是都是很头疼的。
别的,synchronized要害字还使得大量的同步工具处处利用,从而引入了死锁的大概性。好比,java.util.Hashtable和java.util.Vector中提供的要领都是受互斥掩护的,因此除非确实需要利用它们,不然只管不消。开拓者只需要利用java.util.HashMap和java.util.ArrayList即可。虽然,java.util.Collections中的同步要领也利用了synchronized要害字。
尽量可重入更易于打点,但它引入了其他问题。可重入代码制止了线程间数据的共享。思量如下代码(暂时认为Java中的要领是线程安详的):
public Double pi() {
int a = 22;
int b = 7;
return new Double(a / b);
}
不管同时进入该要领的线程有几多,它老是线程安详的。各个线程都维护了属于各个线程的栈,并差异其他线程共享。个中,各个线程在当前要领(包罗静态要领)中建设的要领变量仅属于当前线程,即存储在当前线程的栈中。因此,当线程A和B同时进入上述要领时,它们都将建设a和b。由于上述要领不存在数据共享,因此上述要领是线程安详的。请留意:22/7值同PI值较靠近,但它们不相等。
接下来,看看如何优化上述代码吧。
private Double pi = null;
public Double pi() {
if (pi == null) {
pi = new Double(22 / 7);
}
return pi;
}
#p#副标题#e#
尽量改造后的要领可以或许提高机能,但并不是线程安详的。好比:假如pi为null,并且线程A和B同时进入第4行。因此,线程A和B会同时测试pi是否为空,它们都将返回true。接下来,假如线程A继承执行(线程B由于某种原因被暂挂),然后返回对内存地点的引用。个中,该内存地点含有22/7的功效,即pi值。最后,线程A退出要领。当线程B再次进入第5行时,新的内存地点将包围原先的内存地点(线程A提供的)。这太危险了,并且这种问题往往难于调试。
假如利用ThreadLocal,则不只可以或许担保pi()要领是线程安详,并且可以或许提供机能的改进。 private static ThreadLocal pi = new ThreadLocal();
public Double pi() {
if (pi.get() == null) {
pi.set(new Double(22 / 7));
}
return (Double)pi.get();
}
ThreadLocal类可以或许包裹任何工具,并且可以或许将工具绑定到当前线程,使得它仅仅供当前线程利用。当线程初次执行pi()要领时,由于没有工具绑定到ThreadLocal实例pi上,因此get()要领返回null。借助于set()要领可以或许将工具绑定到当前线程,并且不供其它线程利用。因此,假如差异线程需要常常会见pi()要领,则借助于ThreadLocal不只可以或许担保线程安详,并且可以或许提高机能。
今朝,存在许多关于如何利用ThreadLocal的资源。在Java 1.4之前,ThreadLocal的机能确实很差,可是现已办理了这个问题。别的,由于对ThreadLocal的错误领略,使得许多开拓者对它的误用。留意,上述实例利用ThreadLocal的方法是绝对没问题的。在引入ThreadLocal后,上述要领的行为并未产生改变,可是要领已经是线程安详的了。
#p#分页标题#e#
通过可重入的方法开拓线程安详的代码要求开拓者审慎利用实例变量或静态变量,尤其对付修改那些其他线程需要利用的工具而言。某些场所,利用同步大概更为符合。然而,为识别由于同步而引起的应用机能瓶颈往往只能借助于专业的机能评测东西或负载测试完成。
Web应用中的线程安详
在复习线程安详的常识后,来研究Web应用中是如何线程安详的吧!开拓者通过建设Web页面来操纵数据库。好比,在Web层和业务逻辑层都可以或许操纵RDBMS。本文利用Hibernate将业务模子耐久化到数据库中。在Web层,开拓者可以利用Tapestry、Wicket、Struts、WebWork、JSF、Spring MVC,可能其他运行在Web容器中的Web框架。
至于Web层的详细实现并不是本文的重点。本文将存眷如何打点数据库毗连,这也是Web应用中处理惩罚线程安详问题是常常要思量的资源。数据库毗连工具,好比毗连、功效集、Statement、Hibernate Session,是有状态工具。虽然,它们不是线程安详的,因此不可以或许同时供多个线程会见。在本文前面已经提到,开拓者应只管制止利用同步。无论是synchronized要害字,照旧那些同步类(Hashtable或Vector),应只管制止利用。因此,假如利用可重入,则不消处理惩罚阻塞或死锁。
虽然,通过可重入实现线程安详以会见数据库并不是件简朴的事情。好比,有些开拓者大概会在Servlet容器设置中添加过滤器。因此,在客户请求到来时,过滤器将建设JDBC毗连或Hibernate Session,并借助于ThreadLocal类将它们绑定到当前线程中,从而供业务逻辑利用。假如直接利用J2EE API,则开拓者除了需要做许多同业务逻辑无关的操纵外,还需要打点事务、DB错误等等开拓内容。请留意,这些同业务逻辑无关的操纵的维护事情往往很费时间。
Spring的冲入
一些Java开拓者大概传闻过Spring提供的DAO抽象。虽然,一些开拓者也有大概利用过它。借助于Spring提供的模板,开拓者可以或许利用DAO代码的重用。借助于Spring AOP,开拓者还可以或许利用声明式事务。因此,本文来研究Spring是如何实现以线程安详方法会见RDBMS的。好比,Spring答允以JDBC、Hibernate、JDO、iBATIS、TopLink等方法会见数据库。如下给出的实例是企业应用中很常见的情景。
首先,界说数据源和用于Hibernate SessionFactory。
<bean
id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>WEB-INF/jdbc.properties</value>
</list>
</property>
</bean>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName"><value>${jdbc.driverClassName}</value></property>
<property name="url"><value>${jdbc.url}</value></property>
<property name="username"><value>${jdbc.username}</value></property>
<property name="password"><value>${jdbc.password}</value></property>
</bean>
<bean id="sessionFactory" class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
<property name="dataSource">
<ref bean="dataSource"/>
</property>
<property name="mappingDirectoryLocations">
<list>
<value>classpath:</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">net.sf.hibernate.dialect.HSQLDialect</prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
</bean>
这是利用Hibernate的典范设置,即通过界说的数据源毗连到数据库、通过当地SessionFactory建设Hibernate SessionFactory。接下来,需要界说业务工具(实现对DB的会见)和事务打点器(通过Hibernate Session打点当地事务)。个中,业务工具袒露的要领可以或许在数据库中添加新的记载,而事务打点器可以或许将要领包裹在事务中。它们的界说如下。
public interface CustomerDAO {
public void createCustomer(Customer customer);
}
public class HibernateCustomerDAO implements CustomerDAO {
private HibernateTemplate hibernateTemplate = null;
public void setSessionFactory(SessionFactory sessionFactory) {
this.hibernateTemplate = new HibernateTemplate(sessionFactory, false);
}
public void createCustomer(Customer customer) {
this.hibernateTemplate.save(customer);
}
}
#p#分页标题#e#
开拓者应该已经看到,上述类利用了Spring提供的HibernateTemplate。留意,模板的开拓遵循了业界最佳实践,而且将一些同业务不相关,但J2EE API划定要处理惩罚的那些代码处理惩罚掉了。与此同时,它通过DAO抽象将受查异常转换为非受查异常。虽然,Spring不可是为利用Hibernate提供模板,它还为JDBC、iBATIS、SqlMap、JDO、TopLink提供雷同模板。由于这些模板类及其实例变量实现了可重入,即都是线程安详的,因此答允并发线程同时利用模板。利用这些模板不只可以或许实现代码的重用,还提供了最佳实践。除了以线程安详方法会见DB外,模板还提供了其他许多有意义的内容。好了,来看看如何界说业务工具和事务打点器吧!
<bean id="customerDAOTarget" class="test.usecase.HibernateCustomerDAO">
<property name="sessionFactory"><ref bean="sessionFactory"/></property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.hibernate.HibernateTransactionManager">
<property name="sessionFactory"><ref bean="sessionFactory"/></property>
</bean>
<bean id="customerDAO" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager"><ref bean="transactionManager"/></property>
<property name="target"><ref bean="customerDAOTarget"/></property>
<property name="transactionAttributes">
<props>
<prop key="create*">PROPAGATION_REQUIRED</prop>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
假如开拓者对Spring中事务打点的设置不熟悉,则本文正好满意你们。首先,上述Spring设置片段界说了业务工具HibernateCustomerDAO,它包裹了Hibernate SessionFactory。留意,默认时,Spring中界说的JavaBean都是单例的,HibernateCustomerDAO也不破例。这意味:多个线程大概同时执行createCustomer()要领。
其次,设置了Hibernate事务打点器,它包裹了同一Hibernate SessionFactory实例。在事务打点器每次执行时,它城市完成如下几件工作。其一,查抄Hibernate Session是否绑定到当前线程。假如已绑定,则直接利用它。假如还未绑定,事务打点器将奉告Hibernate SessionFactory建设新的Session,然后将建设的Session绑定到当前线程。其二,假如当前没有处于勾当的事务,则事务打点器将启动新的事务,并将Session包裹进来。不然,直接参加到勾当事务中。
整个进程是通过利用Spring提供的TransactionProxyFactoryBean实现的。虽然,这是一种以声明方法实现的事务打点进程。 TransactionProxyFactoryBean可以或许为业务工具建设署理工具,从而通过事务打点器打点事务。当每次通过署理工具挪用createCustomer()要领时,事务打点器将按照事务属性打点事务。当前,Spring除了提供HibernateTransactionManager事务打点器外,还为JDBC数据源、JDO、TopLink提供了相应的事务打点器。
再来看看业务工具吧!当挪用createCustomer()要领时,HibernateTemplate将查找绑定到当前线程的Hibernate Session。由于上述设置文件片段传入到HibernateTemplate构建器的第二个参数为false,因此假如没有绑定Hibernate Session,则将抛出未受查异常。这对付那些未正确设置事务打点成果的场和出格有用(留意,事务打点器很重要)。一旦事务打点设置好后,Hibernate Session将绑定到当前线程,从而启动事务。请留意,HibernateTemplate不会去查抄事务是否激活,也不会显示地启动或终止事务。也请留意,假如在声明的要领(事务属性中给出的)中抛出了未受查异常,则当前勾当事务将回滚。
结论
最后,来总结一下Spring以线程安详方法实现数据会见吧。通过利用事务打点和衡量ThreadLocal提供的成果,Spring将数据库毗连(JDBC毗连、Hibernate Session、JDO耐久化打点器)绑定到当前线程,从而供DAO模板利用。本文在最开始研究了数据库毗连并没有在线程间共享。Spring不只提供了声明式事务打点、J2EE API抽象、最佳实践,并且其提供的模板是线程安详的。当利用Spring会见DB时,通过可重入实现应用的线程安详是最为靠得住、常见的做法。