`
m635674608
  • 浏览: 5061483 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

Spring单实例、多线程安全、事务解析

    博客分类:
  • java
 
阅读更多
 在使用Spring时,很多人可能对Spring中为什么DAO和Service对象采用单实例方式很迷惑,这些读者是这么认为的:
    DAO对象必须包含一个数据库的连接Connection,而这个Connection不是线程安全的,所以每个DAO都要包含一个不同的Connection对象实例,这样一来DAO对象就不能是单实例的了。
    上述观点对了一半。对的是“每个DAO都要包含一个不同的Connection对象实例”这句话,错的是“DAO对象就不能是单实例”。
    其实Spring在实现Service和DAO对象时,使用了ThreadLocal这个类,这个是一切的核心! 如果你不知道什么事ThreadLocal,请看深入研究java.lang.ThreadLocal类》:。请放心,这个类很简单的。
    要弄明白这一切,又得明白事务管理在Spring中是怎么工作的,所以本文就对Spring中多线程、事务的问题进行解析。

Spring使用ThreadLocal解决线程安全问题:

    Spring中DAO和Service都是以单实例的bean形式存在,Spring通过ThreadLocal类将有状态的变量(例如数据库连接Connection)本地线程化,从而做到多线程状况下的安全。在一次请求响应的处理线程中, 该线程贯通展示、服务、数据持久化三层,通过ThreadLocal使得所有关联的对象引用到的都是同一个变量。 
    参考下面代码,这个是《Spring3.x企业应用开发实战中的例子》,本文后面也会多次用到该书中例子(有修改)。
public class SqlConnection {
    //①使用ThreadLocal保存Connection变量
    privatestatic ThreadLocal <Connection>connThreadLocal = newThreadLocal<Connection>();
    publicstatic Connection getConnection() {
       // ②如果connThreadLocal没有本线程对应的Connection创建一个新的Connection,
       // 并将其保存到线程本地变量中。
       if (connThreadLocal.get() == null) {
           Connection conn = getConnection();
           connThreadLocal.set(conn);
           return conn;
       } else {
           return connThreadLocal.get();
           // ③直接返回线程本地变量
       }
    }
    public voidaddTopic() {
       // ④从ThreadLocal中获取线程对应的Connection
       try {
           Statement stat = getConnection().createStatement();
       } catch (SQLException e) {
           e.printStackTrace();
       }
    }
}
    这个是例子展示了不同线程使用TopicDao时如何使得每个线程都获得不同的Connection实例副本,同时保持TopicDao本身是单实例。

事务管理器:

    事务管理器用于管理各个事务方法,它产生一个事务管理上下文。下文以SpringJDBC的事务管理器DataSourceTransactionManager类为例子。
    我们知道数据库连接Connection在不同线程中是不能共享的,事务管理器为不同的事务线程利用ThreadLocal类提供独立的Connection副本。事实上,它将Service和Dao中所有线程不安全的变量都提取出来单独放在一个地方,并用ThreadLocal替换。而多线程可以共享的部分则以单实例方式存在。

事务传播行为:

    当我们调用Service的某个事务方法时,如果该方法内部又调用其它Service的事务方法,则会出现事务的嵌套。Spring定义了一套事务传播行为,请参考。这里我们假定都用的REQUIRED这个类型:如果当前没有事务,就新建一个事务,如果已经存在一个事务,则加入到的当前事务。参考下面例子(代码不完整):
@Service( "userService")
public class UserService extends BaseService {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private ScoreService scoreService;
   
    public void logon(String userName) {
        updateLastLogonTime(userName);       
        scoreService.addScore(userName, 20);
    }

    public void updateLastLogonTime(String userName) {
        String sql = "UPDATE t_user u SET u.last_logon_time = ? WHERE user_name =?";
        jdbcTemplate.update(sql, System. currentTimeMillis(), userName);
    }

    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("com/baobaotao/nestcall/applicatonContext.xml" );
        UserService service = (UserService) ctx.getBean("userService" );
        service.logon( "tom");

    }
}

@Service( "scoreUserService" )
public class ScoreService extends BaseService{
    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void addScore(String userName, int toAdd) {
        String sql = "UPDATE t_user u SET u.score = u.score + ? WHERE user_name =?";
        jdbcTemplate.update(sql, toAdd, userName);
    }
}
    同时,在配置文件中指定UserService、ScoreService中的所有方法都开启事务。
    上述例子中UserService.logon()执行开始时Spring创建一个新事务,UserService.updateLastLogonTime()和ScoreService.addScore()会加入这个事务中,好像所有的代码都“直接合并”了!

多线程中事务传播的困惑:

    还是上面那个例子,加入现在我在UserService.logon()方法中手动新开一个线程,然后在新开的线程中执行ScoreService.add()方法,此时事务传播行为会怎么样?飞线程安全的变量,比如Connection会怎样?改动之后的UserService 代码大体是:
@Service( "userService")
public class UserService extends BaseService {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private ScoreService scoreService;

    public void logon(String userName) {
        updateLastLogonTime(userName);
        Thread myThread = new MyThread(this.scoreService , userName, 20);//使用一个新线程运行
        myThread .start();
    }

    public void updateLastLogonTime(String userName) {
        String sql = "UPDATE t_user u SET u.last_logon_time = ? WHERE user_name =?";
        jdbcTemplate.update(sql, System. currentTimeMillis(), userName);
    }

    private class MyThread extends Thread {
        private ScoreService scoreService;
        private String userName;
        private int toAdd;
        private MyThread(ScoreService scoreService, String userName, int toAdd) {
            this. scoreService = scoreService;
            this. userName = userName;
            this. toAdd = toAdd;
        }

        public void run() {
            scoreService.addScore( userName, toAdd);
        }
    }

    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("com/baobaotao/multithread/applicatonContext.xml" );
        UserService service = (UserService) ctx.getBean("userService" );
        service.logon( "tom");
       }
}
    这个例子中,MyThread会新开一个事务,于是UserService.logon()和UserService.updateLastLogonTime()会在一个事务中,而ScoreService.addScore()在另一个事务中,需要注意的是这两个事务都被事务管理器放在事务上下文中。
    结论是:在事务属性为REQUIRED时,在相同线程中进行相互嵌套调用的事务方法工作于相同的事务中。如果互相嵌套调用的事务方法工作在不同线程中,则不同线程下的事务方法工作在独立的事务中。
 

底层数据库连接Connection访问问题

    程序只要使用SpringDAO模板,例如JdbcTemplate进行数据访问,一定没有数据库连接泄露问题!如果程序中显式的获取了数据连接Connection,则需要手工关闭它,否则就会泄露!
    当Spring事务方法运行时,事务会放在事务上下文中,这个事务上下文在本事务执行线程中对同一个数据源绑定了唯一一个数据连接,所有被该事务的上下文传播的放发都共享这个数据连接。这一切都在Spring控制下,不会产生泄露。Spring提供了数据资源获取工具类DataSourceUtils来获取这个数据连接.
@Service( "jdbcUserService" )
public class JdbcUserService {
    @Autowired
    private JdbcTemplate jdbcTemplate;
   
    @Transactional
    public void logon(String userName) {
        try {
            Connection conn = jdbcTemplate.getDataSource().getConnection();           
            String sql = "UPDATE t_user SET last_logon_time=? WHERE user_name =?";
            jdbcTemplate.update(sql, System. currentTimeMillis(), userName);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    public static void asynchrLogon(JdbcUserService userService, String userName) {
        UserServiceRunner runner = new UserServiceRunner(userService, userName);
        runner.start();
    }

    public static void reportConn(BasicDataSource basicDataSource) {
        System. out.println( "连接数[active:idle]-[" +
                       basicDataSource.getNumActive()+":" +basicDataSource.getNumIdle()+ "]");
    }

    private static class UserServiceRunner extends Thread {
        private JdbcUserService userService;
        private String userName;

        public UserServiceRunner(JdbcUserService userService, String userName) {
            this. userService = userService;
            this. userName = userName;
        }

        public void run() {
            userService.logon( userName);
        }
    }

    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("com/baobaotao/connleak/applicatonContext.xml" );
        JdbcUserService userService = (JdbcUserService) ctx.getBean("jdbcUserService" );
        JdbcUserService. asynchrLogon(userService, "tom");
    }
}
    在这个例子中,main线程拿到一个UserService实例,获取一个Connection的副本,它会被Spring管理,不会泄露。UserServiceRunner 线程手动从数据源拿了一个Connection但没有关闭因此会泄露。
    如果希望使UserServiceRunner能拿到UserService中那个Connection们就要使用DataSourceUtils类,DataSourceUtils.getConnection()方法会首先查看当前是否存在事务管理上下文,如果存在就尝试从事务管理上下文拿连接,如果获取失败,直接从数据源中拿。在获取连接后,如果存在事务管理上下文则把连接绑定上去。
    实际上,上面的代码只用改动一行,把login()方法中获取连接那行改成就可以做到:
Connection conn = DataSourceUtils.getConnection( jdbcTemplate .getDataSource());    
   需要注意的是:如果DataSourceUtils在没有事务上下文的方法中使用getConnection()获取连接,依然要手动管理这个连接!
    此外,开启了事务的方法要在整个事务方法结束后才释放事务上下文绑定的Connection连接,而没有开启事务的方法在调用完Spring的Dao模板方法后立刻释放。

多线程一定要与事务挂钩么?

    不是!即便没有开启事务,利用ThreadLocal机制也能保证线程安全,Dao照样可以操作数据。但是事务和多线程确实纠缠不清,上文已经分析了在多线程下事务传播行为、事务对Connection获取的影响。

结论:

  • Spring中DAO和Service都是以单实例的bean形式存在,Spring通过ThreadLocal类将有状态的变量(例如数据库连接Connection)本地线程化,从而做到多线程状况下的安全。在一次请求响应的处理线程中, 该线程贯通展示、服务、数据持久化三层,通过ThreadLocal使得所有关联的对象引用到的都是同一个变量。 
  • 在事务属性为REQUIRED时,在相同线程中进行相互嵌套调用的事务方法工作于相同的事务中。如果互相嵌套调用的事务方法工作在不同线程中,则不同线程下的事务方法工作在独立的事务中。
  • 程序只要使用SpringDAO模板,例如JdbcTemplate进行数据访问,一定没有数据库连接泄露问题!如果程序中显式的获取了数据连接Connection,则需要手工关闭它,否则就会泄露!
  • 当Spring事务方法运行时,就产生一个事务上下文,它在本事务执行线程中对同一个数据源绑定了一个唯一的数据连接,所有被该事务上下文传播的方法都共享这个连接。要获取这个连接,如要使用Spirng的资源获取工具类DataSourceUtils。
  • 事务管理上下文就好比一个盒子,所有的事务都放在里面。如果在某个事务方法中开启一个新线程,新线程中执行另一个事务方法,则由上面第二条可知这两个方法运行于两个独立的事务中,但是:如果使用DataSourcesUtils,则新线程中的方法可以从事务上下文中获取原线程中的数据连接!

http://www.xuebuyuan.com/1771946.html

http://www.oschina.net/question/87799_11230

分享到:
评论

相关推荐

    Spring多数据源分布式事务管理

    在实际配置时,我们需要创建多个具体的DataSource实例,并将它们注册到Spring容器中,然后通过AbstractRoutingDataSource来路由请求。 接着,我们来看看Atomikos如何与Spring集成实现分布式事务。Atomikos是遵循JTA...

    Spring系列面试题129道(附答案解析)

    在Spring AOP中,concern指的是应用程序中的业务逻辑代码,而cross-cutting concern是横切关注点,如日志记录、事务管理等,这些关注点贯穿多个业务逻辑。 44、AOP有哪些实现方式? AOP可以通过以下几种方式实现: ...

    Spring源代码解析(八):Spring驱动Hibernate的实现.doc

    这样做是为了在后续的SessionFactory配置过程中,这些资源能与当前线程绑定,确保在多线程环境下的正确性。 然后,`Configuration`对象会被用来加载Hibernate的映射文件和实体类,进行实体的元数据配置。这通常涉及...

    spring3.1完整包

    3.1版本优化了类型安全的事件模型和多线程支持。 6. **org.springframework.jdbc-3.1.0.M1.jar**:Spring的JDBC模块提供了一个数据库操作的抽象层,简化了JDBC代码,处理了连接池、事务管理等复杂问题。3.1版本增加...

    mybatis-spring-1.3.1

    2. SqlSessionTemplate:这是一个线程安全的SqlSession实现,它提供了一种更安全、更易于使用的SqlSession操作方式。开发者可以通过SqlSessionTemplate执行SQL,而无需关心关闭SqlSession等细节。 3. ...

    精通spring(part 4)共分5个part (罗时飞)

    因此,在设计Bean时需要考虑线程安全问题。 #### 6.2 使用Spring Boot简化开发 Spring Boot是Spring框架的一个衍生项目,它简化了Spring应用的初始搭建以及开发过程。Spring Boot提供了自动配置、启动类、嵌入式...

    Struts2 hibernate spring

    4. **数据源和SessionFactory管理**:Spring可以管理Hibernate的数据源和SessionFactory,提供线程安全的SessionFactory实例,简化了数据库连接的创建和关闭。 在`spring-framework-2.5.6.SEC03`版本中,Spring引入...

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

    10.4.1 Spring通过单实例化Bean简化多线程问题 10.4.2 启动独立线程调用事务方法 10.5 联合军种作战的混乱 10.5.1 Spring事务管理器的应对 10.5.2 Hibernate+Spring JDBC混合框架的事务管理 10.6 特殊方法成漏网之鱼...

    精通spring 源代码

    10. **源码分析**:深入阅读Spring源码,可以帮助我们理解其内部设计思想,如事件驱动、设计模式的应用(如单例、工厂、装饰者等)、类加载机制以及线程安全等Java编程基础。 以上只是Spring框架中部分关键知识点的...

    JAVA核心知识点整理,涵盖JAVA基础、集合类、JVM、IO/NIO、多线程、Spring原理等知识

    本文将根据提供的文件信息,对JAVA基础、集合类、JVM、IO/NIO、多线程以及Spring框架的基本原理进行深入解析,旨在帮助读者全面理解这些核心概念。 #### 1. Java基础 Java语言的基础部分包括了变量、数据类型、...

    mybatis-spring

    4. 线程安全:SqlSessionTemplate 是线程安全的,可以直接在多线程环境中使用。 总结,MyBatis-Spring 作为 MyBatis 与 Spring 的结合体,不仅保留了 MyBatis 的灵活性,还利用了 Spring 的强大功能,使得开发者...

    官方原版源码 spring-framework-5.2.9.RELEASE.zip

    2. **性能优化**:通过对源码的学习,开发者可以了解Spring如何进行性能优化,如缓存、多线程、对象池等技术的应用。 3. **扩展性设计**:Spring的模块化和插件化设计思路,对于构建大型复杂系统有重要启示,源码...

    SpringMVC+Spring+hibernate配置

    SpringMVC、Spring和Hibernate是Java开发中三...Spring可以透明地管理Hibernate的SessionFactory,确保在多线程环境下的安全性,并且通过AOP处理事务。这样的组合使得开发者能更专注于业务逻辑,而不是底层的基础设施。

    Spring3.x企业应用开发实战(完整版) part1

    10.4.1 Spring通过单实例化Bean简化多线程问题 10.4.2 启动独立线程调用事务方法 10.5 联合军种作战的混乱 10.5.1 Spring事务管理器的应对 10.5.2 Hibernate+Spring JDBC混合框架的事务管理 10.6 特殊方法成漏网之鱼...

    18 线程作用域内共享变量—深入解析ThreadLocal.pdf

    传统的解决方案包括使用`Atomic`类、`volatile`关键字以及`synchronized`关键字来保证多线程环境下的数据一致性。然而,这些同步机制并不总是最优解,特别是在需要线程内共享变量且避免线程间干扰的情况下。此时,`...

    SPRING面试宝典

    如果一个Bean的实例在多线程环境下被并发访问,开发者需要显式地确保线程安全性,例如通过同步机制。 **3.7 解释Spring框架中的Bean生命周期** Spring框架中的Bean生命周期主要包括以下几个阶段: 1. **实例化**...

    Spring2.5-中文参考手册chm

    9. **批处理处理**:Spring Batch是Spring框架的一个子项目,专注于批处理应用,提供了事务管理、跳过和恢复策略、分割和多线程执行等功能。 10. **Spring Security**:Spring Security是一个强大的安全框架,用于...

    spring-mybatis-1.2.5-src.zip

    - **SqlSessionFactory**:创建SqlSession实例,是线程安全的,可以被多个线程共享。 - **SqlSession**:执行SQL的会话,每次数据库操作都需要创建一个新的SqlSession实例。 4. **Spring对MyBatis的管理** - **...

    4 后台使用Spring中的Bean质量评估193210111党涛1

    在Web应用中,如果Bean被设计为单例,并且需要处理多个用户的请求,那么必须确保这些Bean是线程安全的。Spring提供了一些策略来确保Bean的安全性,如使用线程局部变量、同步方法或避免使用共享状态。 其次,最常见...

Global site tag (gtag.js) - Google Analytics