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模板和回调的示意图: