`
xly1981
  • 浏览: 146544 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

使用spring的线程安全web应用(翻译Thread-safe webapps using Spring)

阅读更多
http://www.javalobby.org/articles/thread-safe/index.jsp

利用servlet容器进行开发工作的开发人员应该知道servlet specifications 。

servlet容器为web.xml里面的对象产生一个单例servlet对象.请求有可能同时发生,这就意味着多个线程也许会同时执行一段代码。这就意味着web应用要特别重视安全问题。

回顾线程安全

首先让我们回顾一个线程安全的含义以及如何使用线程安全。各类文章基本上是这么说的:有段代码是线程安全的,它满足时reentrant 或者有通过互斥方式来保护它同步执行。


synchronized 是java线程安全的基础。但他容易带来阻塞。

阻塞对于web应用来说是一个大问题。阻塞并不是并发带来的唯一问题,还可能会有死锁发生。

除了synchronized 关键字,java还提供了java.util.Hashtable 和java.util.Vector.他们的方法是synchronized 的,所以在必要的时候才能用它们。否则就使用java.util.HashMap 和java.util.ArrayList,还有 java.util.Collections 也可以用。

Reentrance虽然容易管理,但也引入了一批问题。Reentrant 代码阻止数据在线程间共享。我们接下来看下面的代码,来理解线程安全
public Double pi() {
    int a = 22;
    int b = 7;
    return new Double(a / b);
}


现在在执行这个方法时,每一个线程都有自己的独立的 栈区 。当线程进入到方法执行断的时候,一个方法变量在方法代码段中被创建,并保存在线程的 栈区 (静态方法也放在这里)。不同线程执行这段代码时,会有不同的a/b变量。所以这里是线程安全的,因为没有数据共享。


考虑一下下面的例子,多线程情况下只执行一次并可以重用结果:
private Double pi = null;

public Double pi() {
	if (pi == null) {
		pi = new Double(22 / 7);
	}

	return pi;
}


这个地方虽然优化了,但可惜他不是线程安全的。两个线程并发执行的时候同时进入到pi==null这个位置,这样可能会new出一个脏的数据
Consider this example which uses ThreadLocal to make the method pi() again thread-safe while still offering performance gains:

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()方法的时候,实例pi返回的是当前线程的对象。这样的调用是线程安全的。

之前人们对ThreadLocal有很多的怀疑,但是从上面这个例子被很好的保留。

Writing thread-safe code by reentrance requires you to be careful when using instance variables or static variables, especially when you are modifying objects that may be used by other threads. Synchronization may offer a outcome is some use cases. However, testing your application to identify performance bottlenecks that may be introduced by synchronization is only possible by using profiling tools and testing under load.

web应用中的 线程安全


下面我们考虑一下上面的这个事例如何用在线程安全的web应用中。我们新建一批可以有着良好定义的过程,通过这些过程,允许并发访问数据库。我们利用hibernate来做持久化对象模型。

我们现在只关注于数据库访问的线程安全问题,不关注web层页面访问的问题。类似于connections, result sets, statements or Hibernate sessions 的数据库对象都是有状态的对象。他们没有从设计角度实现线程安全。而且不能被多线程同时访问。我们要避免利用synchronization 方式来控制代码,也不用类似于vector和hashtable的同步类,这是为了避免出现死锁。相反,我们通过reentrance来实现线程安全。

Still, implementing thread-safe database access through reentrance remains a tedious task. Some clever people have come up with a solution by adding a filter in the servlet container configuration. Such a filter would amongst other things create a JDBC connection or Hibernate session at the start of the request before the web tier is invoked and bind it to the current thread by means of ThreadLocal for use in the business logic. While this approach would allow us to achieve thread-safe data access there still is the issue of managing transactions and database errors in addition to the required use a lot of boilerplate code in the business logic. Boilerplate code is particularly bad not only because we have to copy and paste it which makes our application hard to maintain but also because it's potentially buggy.

Spring comes to the rescue

Some of you may have heard of the data access abstraction offered by Spring, some may have used it. Spring's data access abstraction is known for declarative transaction demarcation and reuse of data access best practices through templates. One area that is less covered when reviewing Spring is the thread-safe way in which data access is achieved. This achievement is available in Spring for JDBC, Hibernate, JDO, iBatis and TopLink. Let's have look at how this works in our typical use case.

We start by defining a data source and session factory for Hibernate.
<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配置数据源。我们定义数据源来连接数据库和本地会话实例(会话实例由hibernate会话工厂创建)。下一个目标就是添加一个通过hibernate访问数据库的业务对象,并添加一个事务管理器,这个事务管理器利用hibernate会话来管理本地事务。业务对象会暴露出一个在数据库中产生对象的方法,执行这个产生对象的操作的时机是在事务管理器在事务里面包装这个方法的时候。首选我们看一下这个事务对象和他的方法。
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);
    }
}

上面这个例子中,我们看到的HibernateTemplate 是spring提供的。这个template 类实现了下面的最佳实践和抽象的样例代码。... 类似的template类spring也为DBC, iBatis SqlMap, JDO and TopLink这些框架提供了。这些所有的template类和他们的实例变量都是通过reentrance 方式实现线程安全,而且能够安全的被多线程并发访问。这些模板的功能不仅仅是代码和最佳实践复用。更多的是满足我们线程安全的数据访问。下面让我们看看sping里面如何进行建立业务对象和进行事务管理。
<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的事务管理。我们进一步来讨论这个配置文件。第一步,我们的业务对象调用HibernateCustomerDAO,此时的HibernateCustomerDAO 已经装配了hibernate会话工厂实例,注意点sping里面所有的bean都默认为单例的,所以可以看出,多线程执行createCustomer()方法是存在并发的。

第二步,我们注册一个同样组装了hibernate会话工厂的hibernate 事务管理器,这个事务管理器在每次调用的时候,都会做一系列的事情:1.先检查是否存在一个hibernate事务绑定到当前线程,如果有,就使用它,如果没有hibernate会话绑定到当前线程,事务管理器会请求hibernate session 工厂产生一个新的hibernate session,并把这个session 绑定到当前线程。2.依赖于我们自己定义的内容,如果当前没有事务,事务管理器针对hibernate session开始一个新的事务,若已经有事务了,加入到当前的事务中去。

这个动作能够以声明的方式,绑定到spring的TransactionProxyFactoryBean 。TransactionProxyFactoryBean 为通过事务管理器为管理事务的业务对象创建一个代理对象,每次createCustomer()方法都是通过proxy对象来调用的,事务的管理方式依赖于我们配置的事务属性。spring为hibernate、jdbc、djo等数据源提供一系列的事务管理实现

现在我们返回到我们的业务对象。下面部分依赖于spring的AOP。在我们调用createCustomer() 方法是,HibernateTemplate 会去找一个hibernate session绑定到当前线程上,因为我们把false传递给了HibernateTemplate 构造函数的第二个参数,如果没有hibernate session绑定在当前线程上,将会抛出一个unchecked exception,这样也是提供了另外一种针对没有注册transaction management而又想执行createCustomer()方法的安全控制网。我们是在讨论事务,如果transaction management上注册了hibernate session,transaction management应该绑定到了当前线程,而且事务已经处于开始状态。注意,HibernateTemplate 将不会检查一个事务当前是否保持活跃状态,或者这个事务标志为开始或者结束状态。同样要注意当前活动的事务在声明方法中如果抛出了异常,将会执行回滚。通过修改事务属性,我们可以改变事务动作行为,但这部分不属于本文讨论的内容。

结论
我们简单总结一下spring通过线程安全的数据访问功能。通过事务管理和ThreadLocal的强化,spring利用自己的data access templates和线程的ThreadLocal关联起数据库连接工具和当前线程,从这里我们可以看到,在同一个时间数据库连接并不是在多线程之间共享的。spring所做的,只是提供 1.声明式事务管理;2抽象的容器平台级的代码;3.最佳实践,4.线程安全。在通过使用spring来建立数据库连接的时候,我们的应用利用reentrance 来实现线程安全的功能也是顺理成章的。
分享到:
评论

相关推荐

    red5-1.0.0-webapps-demos.zip

    标题中的"red5-1.0.0-webapps-demos.zip"表明这是一个关于Red5服务器的Web应用程序示例的压缩包。Red5是一款开源的流媒体服务器,它支持实时流传输协议(RTMP)、文件录制、播放以及直播等功能。1.0.0是其版本号,...

    spring-mvc-step-by-step.pdf

    Spring MVC是Spring框架的一个重要组成部分,用于构建高性能、可维护的Web应用程序。 **1. 概览** 在开始Spring MVC的学习之前,我们首先了解其基本架构和涵盖的主题。Spring MVC提供了一种模型-视图-控制器(MVC)...

    php-imagick-webapps-master_APP_php_

    【标题】"php-imagick-webapps-master_APP_php_" 指的是一个使用 PHP 和 ImageMagick 库在 Windows 平台上的应用服务项目。这个标题暗示了它可能是一个示例或者教程,用于演示如何在 Azure App Service(Windows ...

    创建简单的WEB应用-maven-Tomcat版.doc

    通过扩展pom.xml文件中的依赖,你可以引入其他框架(如Spring、Hibernate等)以构建更复杂的Web应用。同时,Maven提供了丰富的插件,可以进行代码质量检查、单元测试、集成测试等,确保项目的质量和可维护性。 总的...

    cas-server-webapps.war 单点登录war包

    【标题】"cas-server-webapps.war 单点登录war包" 涉及的核心知识点主要围绕CAS(Central Authentication Service)系统和Web应用程序部署。CAS是一个开源的身份验证框架,它提供了一种安全地验证用户身份的方式,...

    Spring-And-Webapps:基于JAVA和spring-mvc和ioc的web示例应用,以CouchDb和ektorp为orm

    《Spring与Web应用程序:深入解析Java、Spring-MVC与IoC结合CouchDb与Ektorp的实战应用》 在当今的IT行业中,Java作为一款强大的面向对象编程语言,广泛应用于各种复杂系统的开发,尤其是在构建Web应用程序方面。...

    typesafeconfig-for-webapps-1.1.zip

    总的来说,这个"typesafeconfig-for-webapps-1.1"是一个为Web应用提供强大且类型安全配置管理的工具,通过其开源的`typesafeconfig-extensions-master`,开发者不仅可以利用这个库,还能学习到配置管理的最佳实践,...

    tomcat-webapps.rar

    标题“tomcat-webapps.rar”指的是一个包含Tomcat服务器webapps目录中重要组件的压缩文件。这个文件的主要目的是为了防止用户在清理Tomcat工作目录时意外删除一些关键的应用或资源,这些资源对于Tomcat的正常运行和...

    spring-boot-my-tomcat

    在IT行业中,Spring Boot是一个非常流行的Java框架,它简化了基于Spring的应用程序开发。Spring Boot提供了开箱即用的功能,包括内置的HTTP服务器(如Tomcat)、自动配置、starter依赖等,使得开发者能够快速构建可...

    信息办公玉玺学生信息管理系统-webapps.zip

    综上所述,"信息办公玉玺学生信息管理系统-webapps.zip"是一个使用Java技术开发的Web应用程序,主要功能是管理和处理学生信息,涉及到多种技术和方法,需要综合运用服务器端编程、前端开发、数据库管理、网络安全等...

    maven聚合web工程--struts2+spring

    "maven聚合web工程--struts2+spring"这个标题表明我们正在讨论一个使用Maven构建的项目,该项目集成了Struts2和Spring这两个流行的Java Web框架。Struts2是一个用于构建 MVC(模型-视图-控制器)架构的框架,而...

    spring boot 使用国产化 tongweb资源

    Spring Boot 是一个流行的Java开发框架,它简化了创建独立、生产级别的基于Spring的应用程序。它集成了大量的默认配置,使得开发者能够快速上手并构建应用。TongWeb是一款由东软集团开发的企业级应用服务器,它支持...

    mvc+spring4+hibernate4 jar包

    标题中的“mvc+spring4+hibernate4 jar包”指的是一个基于Model-View-Controller(MVC)架构,整合了Spring 4框架和Hibernate 4 ORM库的Java Web应用程序的压缩包。这个包包含了运行在Java应用服务器上的所有必需...

    基于spring-MVC的在线测试系统(客观题)

    Spring-MVC是Spring框架的一部分,它提供了模型-视图-控制器(MVC)的设计模式,使得开发人员能够更有效地构建Web应用程序。 1. **Spring-MVC框架** Spring-MVC是Java Web开发中的一个核心组件,它简化了处理HTTP...

    tomcat-webapps-7.0.76-16.el7_9.noarch.rpm

    官方离线安装包,测试可用。使用rpm -ivh [rpm完整包名] 进行安装

    spring_web_tomcat

    标题 "spring_web_tomcat" 暗示我们即将探讨的是Spring框架在Web开发中的应用,特别是与Tomcat服务器的整合。Spring Web是Spring框架的一部分,它提供了构建Web应用程序的基础结构,而Tomcat是一个流行的开源Java ...

    JSF与Spring完美结合

    在IT行业中,JavaScript Faces (JSF) 和 Spring 框架是两个广泛使用的Java Web开发技术。JSF主要用于构建用户界面,而Spring则提供全面的企业级应用支持,包括依赖注入、事务管理、安全控制等。当这两者结合时,可以...

    SpringBoot项目生成war包

    在Java开发领域,Spring Boot因其简洁的配置和...通过这个过程,我们可以将Spring Boot应用适应传统的Web服务器环境,实现更加灵活的部署策略。在实际开发中,选择jar还是war打包方式应根据项目需求和运行环境来决定。

    Spring编程技术与应用教学课件汇总完整版电子教案全书课件.ppt

    在Tomcat中部署Web应用有两种方式:一是将应用目录复制到`webapps`目录下,Tomcat会自动识别并部署;二是通过Manager App控制台上传WAR包。典型的Web应用结构包括`WEB-INF`目录,其中包含`classes`(存放编译后的类...

    tomcat-admin-webapps-7.0.76-15.el7.noarch.rpm

    官方离线安装包,测试可用。使用rpm -ivh [rpm完整包名] 进行安装

Global site tag (gtag.js) - Google Analytics