NO END FOR LEARNING

Writing blog if you feel tired | 学海无涯 苦写博客

了解Spring Transaction事务管理

| Comments

提供一种统一、抽象的编程模型来管理不同的事务API,如,JavaTransactionAPI(JTA),JDBC,Hibernate,Java Persistence API (JPA)以及Java Data Objects (JDO),是选择Spring Transaction做事务管理最直接的理由。

本地事务和全局事务

全局事务让你可以和多个事务资源工作在一起,比如,关系型数据库,消息队列。

而本地事务则是与某个指定的事务资源联系在一起,比如,与JDBC连接相关的事务。本地事务相对于全局事务更容易使用,但不能跨多个事务资源。管理JDBC连接所写的事务代码不能够在全局事务中使用。

Spring Transaction

Spring Transaction使得开发人员在任何一个环境中都可以使用相同的编程模型。只要写一次代码,就可以在不同环境下的不同的事务策略中使用。最重要的是Spring提供了声明式的事务管理方式,可以通过配置的方式实现事务管理。

Spring事务抽象中一种关键的概念是:事务策略。

一个事务策略,由PlatformTransactionManager接口所定义:

1
2
3
4
5
6
7
8
public interface PlatformTransactionManager {

  TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;

  void commit(TransactionStatus status) throws TransactionException;

  void rollback(TransactionStatus status) throws TransactionException;
}

PlatformTransactionManager是事务管理的抽象层,Spring根据这个抽象层提供许多不同的具体实现。无论是声明式还是编程式的进行事务管理,你都必须正确的定义PlatformTransactionManager的实现。

在使用PlatformTransactionManager的具体实现时,通常都需要一些与对应工作环境的相关知识,比如:JDBC,JTA,Hibernate。

下面是一个JDBC的例子:

1
2
3
4
5
6
7
8
9
10
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="${jdbc.driverClassName}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
</bean>

<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
</bean>

下面是一个Hibernate的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="mappingResources">
            <list>
                <value>org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <value>
                hibernate.dialect=${hibernate.dialect}
            </value>
        </property>
</bean>

<bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory"/>
</bean>

声明式的事务管理

大部分Spring框架的使用者都会采用声明式的事务管理,因为这样做对产品代码的侵入性是最低的。这种声明式的事务管理,使得它可以和Spring的切面编程(AOP)结合在一起,不过即便你不了解AOP,仍然可以使用,因为它几乎是模板式的配置。

这种声明式的事务管理可以允许你在方法级别上指定事务行为。

理解Spring声明式事务的实现

要理解Spring声明式事务的实现,你需要知道的最重要的概念就是,Spring声明式事务的实现是通过Spring的AOP代理。

下面是一个通过XML方式配置事务的例子,方便你理解事务的实现方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!-- this is the service object that we want to make transactional -->
    <bean id="fooService" class="x.y.service.DefaultFooService"/>
    <!-- the transactional advice (what 'happens'; see the <aop:advisor/> bean below) -->
    <tx:advice id="txAdvice" transaction-manager="txManager"> <!-- the transactional semantics... -->
        <tx:attributes>
            <!-- all methods starting with 'get' are read-only -->
            <tx:method name="get*" read-only="true"/>
            <!-- other methods use the default transaction settings (see below) -->
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>
    <!-- ensure that the above transactional advice runs for any execution
        of an operation defined by the FooService interface -->
    <aop:config>
        <aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
    </aop:config>
    <!-- don't forget the DataSource -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
        <property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
        <property name="username" value="scott"/>
        <property name="password" value="tiger"/>
    </bean>
    <!-- similarly, don't forget the PlatformTransactionManager -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!-- other <bean/> definitions here -->
</beans>

对于x.y.service.FooService包中的任何一个服务类的任何一个方法,我都需要它被txManager所管理,任何以get开头的方法都让它在只读事务的上下文中执行,其他的则在默认事务的上下文中执行。

Note:如果DataSourceTransactionManager的bean name,定义为transactionManager,则<tx:advice>中的transaction-manager不用指定。

注解的方式

在真正的开发当中,除了这种xml的方式来指定事务的配置,通过注解的方式来配置事务相对更简单一些。

1
2
3
4
5
6
7
8
9
10
@Transactional
public class DefaultFooService implements FooService {
  Foo getFoo(String fooName);

  Foo getFoo(String fooName, String barName);

  void insertFoo(Foo foo);

  void updateFoo(Foo foo);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd">
    <!-- this is the service object that we want to make transactional -->
    <bean id="fooService" class="x.y.service.DefaultFooService"/>
    <!-- enable the configuration of transactional behavior based on annotations -->
    <tx:annotation-driven transaction-manager="txManager"/>
    <!-- a PlatformTransactionManager is still
     required -->
    <bean id="txManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- (this dependency is defined somewhere else) -->
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!-- other <bean/> definitions here -->
</beans>

Note:同样的道理,如果DataSourceTransactionManager的bean name,定义为transactionManager,则<tx:annotation-driven>中的transaction-manager不用指定。

要真正启动事务管理,仅仅配置@Transactional是不够的,一定要配置<tx:annotation-driven>来启动事务管理行为。

Note:如果你使用Java的配置方式,只需要在@Configuration的类上添加@EnableTransactionManagement来启动事务管理。

Note:@EnableTransactionManagement和<tx:annotation-driven/>在查找@Transactional时,只会在它们定义位置的上下文中查找。意味着,如果你把它们放在了WebApplicationContext中,那么它们只会在Controller中,而不是Service中查找@Transactional。

参考资料:
1.Spring Framework Reference Document, Transaction Management

Servlet多线程安全问题和LocalThread

| Comments

Servlet线程不安全

以下内容摘录自Java™ Servlet Specification,在开始阅读本文章之前,请仔细阅读:

2.1 Request Handling Methods

The handling of concurrent requests to a Web application generally requires that the Web Developer design servlets that can deal with multiple threads executing within the service method at a particular time.
Generally the Web container handles concurrent requests to the same servlet by concurrent execution of the service method on different threads.

2.2 Number of Instances

For a servlet not hosted in a distributed environment (the default), the servlet container must use only one instance per servlet declaration. However, for a servlet implementing the SingleThreadModel interface, the servlet container may instantiate multiple instances to handle a heavy request load and serialize requests to a particular instance. …

2.3.3.1 Multithreading Issues

Although it is not recommended, an alternative for the Developer is to implement the SingleThreadModel interface which requires the container to guarantee that there is only one request thread at a time in the service method. A servlet container may satisfy this requirement by serializing requests on a servlet, or by maintaining a pool of servlet instances. If the servlet is part of a Web application that has been marked as distributable, the container may maintain a pool of servlet instances in each JVM that the application is distributed across.

For servlets not implementing the SingleThreadModel interface, if the service method (or methods such as doGet or doPost which are dispatched to the service method of the HttpServlet abstract class) has been defined with the synchronized keyword, the servlet container cannot use the instance pool approach, but must serialize requests through it. It is strongly recommended that Developers not synchronize the service method (or methods dispatched to it)

默认情况下,非分布式系统,Servlet容器只会维护一个Servlet的实例,当多个请求到达同一个Servlet,Servlet容器会启动多个线程分配给不同请求来执行同一个Servlet实例中的服务方法。为什么这么做?有效利用JVM允许多个线程访问同一个实例的特性,来提高服务器性能。因为,无论是同步线程对Servlet的调用,还是为每一个线程初始化一个Servlet实例,都会带来巨大的性能问题。

这也就是为什么Servlet会存在多线程安全问题。

大部分线程安全问题出现的原因都是Servlet实现者在不经意间创建了一个Servlet的实例变量(成员变量),而导致多个线程公有这个实例变量,存在不同阶段对该变量的读写操作。

预防它很简单:就是避免这样写,用方法中的本地变量替代它。

“Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。”
—深入理解Java虚拟机

ThreadLocal

那如果你希望定义一个变量,让每一个线程都拥有不同的拷贝,应该怎么办?答案是ThreadLocal。

ThreadLocal是Java语言包提供的一个实现类,与其命名ThreadLocal,叫它thread-local variables更合适。和普通变量不同,通过该对象的set和get方法,可以给每一个调用它的线程保存一个独立的变量的拷贝。什么意思?也就是说,该变量保存下来的变量和当时调用该方法的线程是绑定的,不同线程的值是不一样的。

在Java Doc中介绍过:定义该变量的典型方法是private static

1
private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>();

当要保存时,调用它的set方法,获取时,调用get方法。

1
2
threadId.set(10);
threadId.get();

下面看一个例子,单例类中定义了两个变量,实例变量和ThreadLocal变量,多线程读写,并随机等待一段时间,得到的结果会是普通实例变量和time的值不一致,而threadLocal是一致的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package me.zeph.relations;

import java.util.Random;

public class Singleton {

  private int value;

  private static Singleton instance;

  private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();

  private Singleton() {
  }

  public void set(int value) {
      threadLocal.set(value);
  }

  public int get() {
      return threadLocal.get();
  }

  public static synchronized Singleton getInstance() {
      if (instance == null) {
          instance = new Singleton();
      }
      return instance;
  }

  public int getValue() {
      return value;
  }

  public void setValue(int value) {
      this.value = value;
  }

  public static void main(String args[]) {
      final Singleton singleton = Singleton.getInstance();
      for (int i = 0; i < 10; i++) {
          Thread thread = new Thread() {
              @Override
              public void run() {
                  int time = new Random(System.currentTimeMillis()).nextInt(2000);
                  singleton.set(time);
                  singleton.setValue(time);
                  try {
                      Thread.sleep(time);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  System.out.println(time + "---ThreadLocal---" + singleton.get());
                  System.out.println(time + "---NonThreadLocal-----" + singleton.getValue());
              }
          };
          thread.start();
          try {
              Thread.sleep(1000);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
      }
  }
}

了解到它的作用和如何使用之后,你肯定在想,它是怎么实现的?

一种办法是:在这个对象的里面存放一个map对象,map对象的key就是Thread.currentThread()的一些信息,value就是对应的值。这是最显而易见而直接的实现方式。

但是,它不是这么实现的!!!是反过来的!!!

在当前的线程对象里面存放一个Map,Map的key是当前的ThreadLocal对象,value是对应的值。

那么当程序中有多个ThreadLocal是就不是每个threadLocal对象维护一个线程的map,而是每个线程有一个map来维护所有的ThreadLocal。

这么做有什么好处?

我的猜测是,资源释放问题,如果是第一种方式,线程已经完成了它的任务,但是ThreadLocal仍然保存它的引用,那么线程资源就不会立刻释放(根据不同的垃圾回收策略,可能不同)。

以上只是Servlet线程安全问题中一种常见情况,Servlet线程安全问题还有很多,比如Session的访问,但重点是,需要大家意识到Servlet是线程不安全的,于是在编写代码的时候一定要多思考,这样写是否存在线程安全问题。

参考资料:
1.Servlet Specification
2.深入理解Java虚拟机