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

发布于 2014-04-07 | 更新于 2020-09-20

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

我们知道,一般的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 JDBC或iBatis

org.springframework.jdbc.datasource.DataSourceUtils

Hibernate 3.0

org.springframework.orm.hibernate3.SessionFactoryUtils

JPA

org.springframework.orm.jpa.EntityManagerFactoryUtils

JDO

org.springframework.orm.jdo.PersistenceManagerFactoryUtils

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

20140407-Spring01

本文作者: arthinking

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

版权声明: 版权归作者所有,未经许可不得转载,侵权必究!联系作者请加公众号。

×
IT宅

关注公众号及时获取网站内容更新。