`
mingweizheng
  • 浏览: 8938 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
最近访客 更多访客>>
文章分类
社区版块
存档分类
最新评论

开发线程安全的Spring Web应用

阅读更多

前言
  如果开发者正开发或维护基于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;
}
  尽管改进后的方法能够提高性能,但并不是线程安全的。比如:如果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后,上述方法的行为并未发生改变,但是方法已经是线程安全的了。
  通过可重入的方式开发线程安全的代码要求开发者谨慎使用实例变量或静态变量,尤其对于修改那些其他线程需要使用的对象而言。某些场合,使用同步可能更为合适。然而,为识别由于同步而引起的应用性能瓶颈往往只能借助于专业的性能评测工具或负载测试完成。
  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错误等等开发内容。请注意,这些同业务逻辑无关的操作的维护工作往往很费时间。
共2页。 1 2 8 :
  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);
}
}
  开发者应该已经看到,上述类使用了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时,通过可重入实现应用的线程安全是最为可靠、常见的做法

分享到:
评论

相关推荐

    开发线程安全的SpringWeb应用-pdf清晰

    教你怎么开发线程安全的SpringWeb应用.

    线程中获取spring 注解bean

    6. **RequestScope**:在Web应用中,如果bean需要在一次HTTP请求的生命周期内保持一致,可以使用`@Scope("request")`。这样,每次请求都会创建新的bean实例,而不会影响其他线程。 理解并熟练掌握这些方法,有助于...

    Java Web应用开发项目教程(附电子教案,程序源代码,习题答案)

    《Java Web应用开发项目教程》是一本以实践为导向的教程,旨在帮助读者深入理解并掌握Java Web开发技术。教程通过完整的案例,采用模块化的教学方式,将复杂的Web应用开发过程分解为可操作的步骤,逐步引导学习者...

    servlet与Struts action线程安全问题分析

    总之,理解和处理Servlet和Struts Action的线程安全问题对于构建高性能、可靠的Java Web应用至关重要。通过避免使用实例变量、采用同步控制、设计无状态组件以及利用框架特性,可以有效地防止线程安全问题的发生。

    WEB多线程开发

    3. **Struts**:Struts是一个基于MVC(Model-View-Controller)设计模式的Java EE框架,用于简化Web应用的开发。在Struts中,控制器组件通常由Action类实现,这些Action类在处理请求时可能会涉及多线程。开发者需要...

    spring2与quartz在Web整合

    Spring 2 和 Quartz 是两个在 Java Web 开发中非常重要的框架。Spring 是一个全面的开源应用框架,提供了依赖注入(DI)、面向切面编程(AOP)以及丰富的数据访问和事务管理功能。Quartz,则是一个功能强大的作业...

    基于Spring+Ibatis的安全线程实现

    在IT行业中,安全性和线程管理是任何应用开发的核心部分,尤其是在使用Spring和Ibatis这样的流行框架时。本文将深入探讨如何在Spring框架下结合Ibatis实现...这些知识点的掌握将有助于构建健壮、高效且安全的Web应用。

    多线程及spring相关面试专题及答案.zip

    3. **Spring MVC**:Spring MVC是Spring框架的一部分,用于构建Web应用程序,提供了模型-视图-控制器的设计模式。 4. **依赖注入**:Spring的核心特性之一,通过容器管理对象及其依赖关系,增强了代码的可测试性...

    springwebflux的demo

    Spring WebFlux 项目"web-flux-demo"是一个学习和实践反应式Web开发的起点。通过这个项目,你可以了解如何创建、配置和运行一个基于WebFlux的应用,以及如何处理HTTP请求和响应。同时,你还能深入理解反应式编程模型...

    利用Java开发高性能、高并发Web应用

    12. **安全机制**:Spring Security或Apache Shiro为Web应用提供认证和授权,防止XSS、CSRF等安全攻击。 理解并掌握这些知识点,是开发高性能、高并发Java Web应用的基础。实践中,还需要根据具体业务场景灵活运用...

    spring boot web demo

    在这个项目中,我们将探讨Spring Boot的核心特性以及如何利用它来开发Web应用。 1. Spring Boot概述:Spring Boot是由Pivotal Team创建的,其目标是简化Spring应用程序的初始设置和配置。它提供了预配置的依赖,...

    spring4d 一款delphi应用开发框架

    它不仅适用于桌面应用,还可以用于开发移动和Web应用程序,支持多种 Delphi 平台,包括 XE10 及其后续版本。 **2. 依赖注入(Dependency Injection)** Spring4D 的核心组件之一是依赖注入容器,它允许开发者声明...

    Spring并发访问的线程安全性问题.docx

    在Spring MVC中,...总之,理解并处理Spring MVC中的线程安全问题是开发高并发Web应用的关键。合理的架构设计、避免实例变量、使用线程安全的数据结构以及合理配置bean的生命周期,都能帮助我们构建更健壮的系统。

    Spring.3.x企业应用开发实战(完整版).part2

    9.2.5 Spring使用ThreadLocal解决线程安全问题 9.3 Spring对事务管理的支持 9.3.1 事务管理关键抽象 9.3.2 Spring的事务管理器实现类 9.3.3 事务同步管理器 9.3.4 事务传播行为 9.4 编程式的事务管理 9.5 使用XML...

    Spring3.X企业应用开发实战pdf扫描版+源码光盘

    4. **Web MVC**:Spring的Model-View-Controller架构提供了灵活的Web应用开发框架,支持自定义HandlerMapping和ViewResolver,以及数据绑定和验证功能。 5. **Spring Expression Language (SpEL)**:Spring3.X引入...

    Java Web整合开发王者归来

    6. 安全性:随着互联网安全问题的日益突出,Java Web应用程序的安全性也越来越受到重视。所以,书中可能会包含关于Web应用程序安全性的章节,涉及诸如认证、授权、数据加密、防止SQL注入、XSS攻击等安全实践。 7. ...

    基于 Spring Boot 构建 的 Java Web 平台企业级单体应用快速开发框架.zip

    Spring Boot 是一个由 Pivotal Team 开发的 Java 框架,旨在简化新 Spring 应用的初始搭建以及开发过程。它集成了大量常用的第三方库配置,如 JDBC、MongoDB、JPA、RabbitMQ、Quartz 等,大大减少了开发时的配置工作...

    开发Spring.Struts.Hibernate应用

    在IT行业中,Spring、Struts和Hibernate是三个非常重要的开源框架,它们分别专注于不同层面的Web应用程序开发。本文将深入探讨这三个框架的核心概念、应用场景以及它们如何协同工作以构建高效的企业级应用。 **...

    Java学习资料 包括Javaee初阶,web前端,javascript,线程 Spring全家桶等等这些代码

    Spring MVC是用于构建Web应用的模块,Spring Data简化了数据库操作,而Spring Security则专注于应用安全。 文件名“java-ee_code-master”可能是一个包含JavaEE项目的源码仓库,可能包含了上述各个领域的示例代码,...

Global site tag (gtag.js) - Google Analytics