0%
这是一片思考的空间 -- arthinking
Spring 重构&代码整洁之道 软件设计 JVM 并发编程 数据结构与算法 分布式 存储 网络 微服务 设计模式
Java技术栈 - 涉及Java技术体系

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 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

欢迎关注我的其它发布渠道

订阅IT宅
内功修炼
Java技术栈
Java架构杂谈是IT宅精品文章公众号,欢迎订阅:
📄 网络基础知识:两万字长文50+张趣图带你领悟网络编程的内功心法 📄 HTTP发展史:三万长文50+趣图带你领悟web编程的内功心法 📄 HTTP/1.1:可扩展,可靠性,请求应答,无状态,明文传输 📄 HTTP/1.1报文详解:Method,URI,URL,消息头,消息体,状态行 📄 HTTP常用请求头大揭秘 📄 HTTPS:网络安全攻坚战 📄 HTTP/2:网络安全传输的快车道 📄 HTTP/3:让传输效率再一次起飞 📄 高性能网络编程:图解Socket核心内幕以及五大IO模型 📄 高性能网络编程:三分钟短文快速了解信号驱动式IO 📄 高性能网络编程:彻底弄懂IO复用 - IO处理杀手锏,带您深入了解select,poll,epoll 📄 高性能网络编程:异步IO:新时代的IO处理利器 📄 高性能网络编程:网络编程范式 - 高性能服务器就这么回事 📄 高性能网络编程:性能追击 - 万字长文30+图揭秘8大主流服务器程序线程模型
📄 Java内存模型:如果有人给你撕逼Java内存模型,就把这些问题甩给他 📄 一文带你彻底理解同步和锁的本质(干货) 📄 AQS与并发包中锁的通用实现 📄 ReentrantLock介绍与使用 📄 ReentrantReadWriteLock介绍与使用 📄 ReentrantLock的Condition原理解析 📄 如何优雅的中断线程 📄 如何优雅的挂起线程 📄 图解几个好玩的并发辅助工具类 📄 图解BlockingQueue阻塞队列
📄 消息队列那么多,为什么建议深入了解下RabbitMQ? 📄 高并发异步解耦利器:RocketMQ究竟强在哪里? 📄 Kafka必知必会18问:30+图带您看透Kafka
📄 洞悉MySQL底层架构:游走在缓冲与磁盘之间 📄 SQL运行内幕:从执行原理看调优的本质 📄 洞悉Redis技术内幕:缓存,数据结构,并发,集群与算法