Sping中使用单实例化简化多线程的相关实现 事务 模板 回调

Sping中使用单实例化简化多线程的相关实现 事务 模板 回调


好的架构,如同一部辉煌的史诗,会不自觉的发自内心的赞叹,犹如耳边响起雄壮的交响曲,这也是学习源代码的一种乐趣。

我们知道,一般的Web容器中,一般一个HTTP请求对应创建一个独立的线程进行处理(大多数Web容器采用共享线程池的方式),所以Bean自然也是运行于多线程的环境下的,而在绝大多数情况下,Spring的Bean都是单实例的,为了让但单实例的Bean不存在多线程并发访问的问题,一般都是讲有状态的变量存到ThreadLocal中的。

什么是ThreadLocal

在JDK1.2的版本就提供给了java.lang.ThreadLocal,ThreadLocal为解决多线程的程序并发提供了一种新的思路。ThreadLocal为每一个使用该变量的线程分类一个独立的变量副本。

ThreadLocal只有四个方法:

  • void set(Object value): 设置当前线程的线程局部变量值
  • public Object get(): 该方法返回当前线程所对应的线程局部变量
  • public void remove(): 将当前线程局部变量的值删除
  • protected Object initialValue(): 返回该线程局部变量的初始化值,改方法是protected的,是为了让子类覆盖而设计的。

与Thread同步机制对比,同步机制采用改了“以时间换空间”的方式:访问串行化,对象共享化。而ThreadLocal采用了“以空间换时间”的方式:访问并行化,对象独享化。

Spring对事务管理的支持:

类似于DAO为不同持久化实现提供模板类一样,Spring也提供了事务模板类Transaction Template,配合事务回调TransactionCallback指定具体的持久化操作,就可以通过编程方式实现事务的管理,而无需关注资源获取,复用,释放,事务同步和异常处理的操作这些也是存放到ThreadLocal中的,模板类在内部通过资源获取工具类间接访问TransactionSynchronizationManager中的线程绑定资源的,所以如果Dao使用模板类进行持久化操作,这些Dao就可以配置成Singleton,当然也可以直接通过资源获取工具类访问线程相关的资源,下面是TransactionSynchronizationManager中保存的一些资源变量的定义:

// 用于保存每个事务线程对应的Connection或Session等类型的资源
    private static final ThreadLocal<Map<Object, Object>> resources =
      new NamedThreadLocal<Map<Object, Object>>("Transactional resources");

    private static final ThreadLocal<List<TransactionSynchronization>> synchronizations =
      new NamedThreadLocal<List<TransactionSynchronization>>("Transaction synchronizations");
    // 用于保存每个事务线程对应事务的名称
    private static final ThreadLocal<String> currentTransactionName =
      new NamedThreadLocal<String>("Current transaction name");
    // 用于保存每个事务线程对应事务的read-only状态
    private static final ThreadLocal<Boolean> currentTransactionReadOnly =
      new NamedThreadLocal<Boolean>("Current transaction read-only status");
    // 用于保存每个事务线程对应事务的隔离级别
    private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
      new NamedThreadLocal<Integer>("Current transaction isolation level");
    // 用于保存每个事务线程对应事务的激活态
    private static final ThreadLocal<Boolean> actualTransactionActive =
      new NamedThreadLocal<Boolean>("Actual transaction active");

可见TransactionSynchronization将Dao, Service类中影响线程安全的所有状态统一抽取到该类中,并使用ThreadLocal进行替换了。

关于事务隔离级别:

这个状态也是每个线程都会有自己的事务隔离级别的状态,保存在TransactionSynchronizationManager中的currentTransactionIsolationLevel变量中的。

为什么要提供事务的隔离级别呢,是因为尽管数据库提供了锁的DML操作方式(像Oracle数据库中的5种锁:行共享锁定,行独占锁定,表共享锁定,表共享行独占,表独占),但是直接使用锁管理是非常麻烦的,因此数据库为用户提供了自动锁机制,用户只要指定会话的事务隔离级别,数据库就会分析事务中的sql语句,然后自动为事务操作的数据资源添加上适合的锁,并且数据库还会自动维护这些锁,适当的时候进行升级以优化系统系能。

下面是ANSI/ISO SQL 92标准定义的4个等级的事务隔离级别:

隔离级别脏读不可重复读幻象读第一类丢失更新第二类丢失更新
READ UNCOMMITED允许允许允许不允许允许
READ COMMITED不允许允许允许不允许允许
REPEATABLE READ不允许不允许允许不允许不允许
SERIALIZABLE不允许不允许不允许不允许不允许

READ UNCOMMITED隔离级别的数据库拥有最高的并发性和吞吐量,使用SERIALIZABLE隔离级别的数据库并发性低。

JDBC中设置事务隔离级别:

可以通过Connection的getMetaData()方法得到DatabaseMetaData对象,进而查看其对事务隔离级别的支持状况:

supportsTransactions()

supportTransactionIsolationLevel(int level)

Connection默认情况下是自动提交的,为了让多个sql当成一个事务,需要:

  • Connection的setAutoCommit(false)阻止自动提交
  • Connectiond setTransactionIsolation()设置事务的隔离级别

Spring事务管理SPI(Service Provider Interface)的抽象层主要包括3个接口:

TransactionDefinition定义了Spring兼容的事务属性,包括事务隔离,事务传播,事务超时,只读状态等,可以通过XML或者注解元数据的方式为一个有事务要求的方法配制事务属性。

TransactionStatus代表一个事务的具体运行状态,改接口又继承于SavepointManager接口,SavepointManage接口基于JDBC3.0保存点的分段事务控制能力提供了嵌套事务的机制。

PlatformTransactionManager只定义了三个SPI高层次的接口方法,可以把PlatformTransactionManager看出Spring容器中普通的bean:

TransactionStatus getTransaction(TransactionDefinition difinition)

rommit(TransactionStatus status)

roolback(TransactionStatus status)

默认的情况下,dataSource的数据源的autoCommit被设置为true,此时所有通过JdbcTemplate执行的语句马上提交,没有事务。对于强调读速度的应用,数据库本身可能就不支持事务,如使用MyISAM引擎的MySQL数据库,这时,就无需在Spring应用中配置事务管理器了。

接下来看一下JdbcTemplate中的数据库连接的保存方式:

看一下JdbcTemplate中的execute方法获取数据库连接的相关代码:

public <T> T execute(ConnectionCallback<T> action) throws DataAccessException {
        Assert.notNull(action, "Callback object must not be null");

        Connection con = DataSourceUtils.getConnection(getDataSource());

可以发现,这里也是通过Spring提供的线程绑定资源的工具类DataSourceUtils获取到数据库连接的,看到这个getConnection方法的实现:

public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
        try {
            return doGetConnection(dataSource);
        }
        catch (SQLException ex) {
            throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex);
        }
    }

这里又调用了doGetConnection方法:

public static Connection doGetConnection(DataSource dataSource) throws SQLException {
    Assert.notNull(dataSource, "No DataSource specified");

    ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
  	// 如果holder不为空,并且存在连接或者holder是事务同步的,则获取或者创建一个连接
    if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
      conHolder.requested();
      if (!conHolder.hasConnection()) {
        logger.debug("Fetching resumed JDBC Connection from DataSource");
        conHolder.setConnection(dataSource.getConnection());
      }
      return conHolder.getConnection();
    }
    // Else we either got no holder or an empty thread-bound holder here.

    logger.debug("Fetching JDBC Connection from DataSource");
    Connection con = dataSource.getConnection();

  	// 如果当前线程是支持事务同步的,则进行一些事务同步管理器的初始化工作
    if (TransactionSynchronizationManager.isSynchronizationActive()) {
      logger.debug("Registering transaction synchronization for JDBC Connection");
      // Use same Connection for further JDBC actions within the transaction.
      // Thread-bound object will get removed by synchronization at transaction completion.
      ConnectionHolder holderToUse = conHolder;
      if (holderToUse == null) {
        holderToUse = new ConnectionHolder(con);
      }
      else {
        holderToUse.setConnection(con);
      }
      holderToUse.requested();
      TransactionSynchronizationManager.registerSynchronization(
          new ConnectionSynchronization(holderToUse, dataSource));
      holderToUse.setSynchronizedWithTransaction(true);
      if (holderToUse != conHolder) {
        TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
      }
    }

    return con;
  }

可以发现,实际上,这里也是通过获取TransactionSynchronizationManager中的ThreadLocal变量中保存的ConnectionHolder来获得数据库连接的,如果该holder里面还没有连接,并且该holder是事务同步的,则从数据源中获取一个,并设置到该holder中。

另外,在看到JdbcTemplate的execute方法中最后:

finally {
            DataSourceUtils.releaseConnection(con, getDataSource());
        }

通过DataSourceUtils.releaseConnection方法释放了资源:

public static void doReleaseConnection(Connection con, DataSource dataSource) throws SQLException {
    if (con == null) {
      return;
    }

    if (dataSource != null) {
      ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
      if (conHolder != null && connectionEquals(conHolder, con)) {
        // It's the transactional Connection: Don't close it.
  			// 如果是事务连接,则不立即关闭,而是释放,留给后续的事务处理
        conHolder.released();
        return;
      }
    }

    // Leave the Connection open only if the DataSource is our
    // special SmartDataSoruce and it wants the Connection left open.
    if (!(dataSource instanceof SmartDataSource) || ((SmartDataSource) dataSource).shouldClose(con)) {
      logger.debug("Returning JDBC Connection to DataSource");
      con.close();
    }
  }

这样,Spring将相同的数据访问流程固化到模板类中,并将数据访问中固定和变换的部分分开,同时保证模板类是线程安全的,以便多个数据访问线程共享模板实例,固定部分在模板中已经准备好,而变化的部分通过回调接口开发出来,用于定义具体数据访问和结果返回的操作。这样我们通过使用Jdbc模板类类,就保证了资源使用的正确性,防止因忘记进行资源释放而引起的资源泄露问题。

当需要脱离模板类,手工操作底层持久技术的原生API时,就需要通过使用Spring提供的线程绑定资源获取工具类获取资源了,而不应该直接从DataSource或SessionFactory中获取,因为后者不能获得本线程相关的资源,无法让数据操作参与到本线程相关的事务环境中。

Spring为不同的持久化技术提供了一套从TransactionSynchronizationManager中获取对应线程绑定资源的工具类,如下表:

下面是Spring DAO模板和回调的示意图:

20140407-Spring01

本文链接: http://www.itzhai.com/sping-zhong-shi-yong-dan-shi-li-hua-jian-hua-duo-xian-cheng-di-xiang-guan-shi-xian-shi-wu-mo-ban-hui-diao.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值