在我的博客spring事务源码解析中,提到了一个很关键的点:将connection绑定到当前线程来保证这个线程中的数据库操作用的是同一个connection。但是没有细致的讲到如何绑定,以及为什么这么绑定;另外也没有讲到连接池的相关问题:如何从连接池获取,如何归还连接到连接池等等。那么下面就请听我慢慢道来。

ThreadLocal

  讲spring事务之前,我们先来看看ThreadLocal,它在spring事务中是占据着比较重要的地位;不管你对ThreadLocal熟悉与否,且都静下心来听我唐僧般的念叨。

  先强调一点:ThreadLocal不是用来解决共享变量问题的,它与多线程的并发问题没有任何关系。

  基本介绍

    当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本,如下例:

public class ThreadLocalTest
{
    ThreadLocal<Long> longLocal = new ThreadLocal<Long>();
    ThreadLocal<String> stringLocal = new ThreadLocal<String>();

    public void set()
    {
        longLocal.set(1L);
        stringLocal.set(Thread.currentThread().getName());
    }

    public long getLong()
    {
        return longLocal.get();
    }

    public String getString()
    {
        return stringLocal.get();
    }

    public static void main(String[] args) throws InterruptedException
    {
        final ThreadLocalTest test = new ThreadLocalTest();

        test.set();     // 初始化ThreadLocal
        for (int i=0; i<10; i++)
        {
            System.out.println(test.getString() + " : " + test.getLong() + i);
        }

        Thread thread1 = new Thread(){
            public void run() {
                test.set();
                for (int i=0; i<10; i++)
                {
                    System.out.println(test.getString() + " : " + test.getLong() + i);
                }
            };
        };
        thread1.start();

        Thread thread2 = new Thread(){
            public void run() {
                test.set();
                for (int i=0; i<10; i++)
                {
                    System.out.println(test.getString() + " : " + test.getLong() + i);
                }
            };
        };
        thread2.start();
    }
}

    执行结果如下

    可以看到,各个线程的longLocal值与stringLocal值是相互独立的,本线程的累加操作不会影响到其他线程的值,真正达到了线程内部隔离的效果。

  源码解读

    这里我就不进行ThreadLocal的源码解析,建议大家去看我参考的博客,个人认为看那两篇博客就能对ThreadLocal有个很深地认知了。

    做个重复的强调(引用[Java并发包学习七]解密ThreadLocal中的一段话):

Thread与ThreadLocal对象之间的引用关系图
 
看了ThreadLocal源码,不知道大家有没有一个疑惑:为什么像上图那么设计? 如果给你设计,你会怎么设计?相信大部分人会有这样的想法,我也是这样的想法:
  ”每个ThreadLocal类创建一个Map,然后用线程的ID作为Map的key,实例对象作为Map的value,这样就能达到各个线程的值隔离的效果“
JDK最早期的ThreadLocal就是这样设计的。(不确定是否是1.3)之后ThreadLocal的设计换了一种方式,也就是目前的方式,那有什么优势了:
  1、这样设计之后每个Map的Entry数量变小了:之前是Thread的数量,现在是ThreadLocal的数量,能提高性能,据说性能的提升不是一点两点(没有亲测)
  2、当Thread销毁之后对应的ThreadLocalMap也就随之销毁了,能减少内存使用量。

Spring事务中的ThreadLocal

  最常见的ThreadLocal使用场景为 用来解决数据库连接、Session管理等,那么接下来我们就看看spring事务中ThreadLocal的应用

ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext-jdbc.xml");
DaoImpl daoImpl = (DaoImpl) ac.getBean("daoImpl");
System.out.println(daoImpl.insertUser("yes", 25));

  只要某个类的方法、类或者接口上有事务配置,spring就会对该类的实例生成代理。所以daoImpl是DaoImpl实例的代理实例的引用,而不是DaoImpl的实例(目标实例)的引用;当我们调用目标实例的方法时,实际调用的是代理实例对应的方法,若目标方法没有被@Transactional(或aop注解,当然这里不涉及aop)修饰,那么代理方法直接反射调用目标方法,若目标方法被@Transactional修饰,那么代理方法会先执行增强(例如判断当前线程是否存在connection,不存在则新建并绑定到当前线程等等),然后通过反射执行目标方法,最后回到代理方法执行增强(例如,事务回滚或事务提交、connection归还到连接池等等处理)。这里的绑定connection到当前线程就用到了ThreadLocal,我们来看看源码

@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
    Connection con = null;

    try {

        if (txObject.getConnectionHolder() == null ||
                txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
            // 从连接池获取一个connection
            Connection newCon = this.dataSource.getConnection();
            if (logger.isDebugEnabled()) {
                logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
            }
            // 包装newCon,并赋值到txObject,并标记是新的ConnectionHolder
            txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
        }

        txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
        con = txObject.getConnectionHolder().getConnection();

        Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
        txObject.setPreviousIsolationLevel(previousIsolationLevel);

        // Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
        // so we don't want to do it unnecessarily (for example if we've explicitly
        // configured the connection pool to set it already).
        if (con.getAutoCommit()) {
            txObject.setMustRestoreAutoCommit(true);
            if (logger.isDebugEnabled()) {
                logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
            }
            con.setAutoCommit(false);
        }
        txObject.getConnectionHolder().setTransactionActive(true);

        int timeout = determineTimeout(definition);
        if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
            txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
        }

        // 若是新的ConnectionHolder,则将它绑定到当前线程中
        // Bind the session holder to the thread.
        if (txObject.isNewConnectionHolder()) {
            TransactionSynchronizationManager.bindResource(getDataSource(), txObject.getConnectionHolder());
        }
    }

    catch (Throwable ex) {
        if (txObject.isNewConnectionHolder()) {
            DataSourceUtils.releaseConnection(con, this.dataSource);
            txObject.setConnectionHolder(null, false);
        }
        throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
    }
}
/**
 * Bind the given resource for the given key to the current thread.
 * @param key the key to bind the value to (usually the resource factory)
 * @param value the value to bind (usually the active resource object)
 * @throws IllegalStateException if there is already a value bound to the thread
 * @see ResourceTransactionManager#getResourceFactory()
 */
public static void bindResource(Object key, Object value) throws IllegalStateException {        //key:通常指资源工厂,也就是connection工厂,value:通常指活动的资源,也就是活动的ConnectionHolder

    // 必要时unwrap给定的连接池; 否则按原样返回给定的连接池。
    Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
    Assert.notNull(value, "Value must not be null");
    Map<Object, Object> map = resources.get();
    // set ThreadLocal Map if none found   如果ThreadLocal Map不存在则新建,并将其设置到resources中
    // private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<Map<Object, Object>>("Transactional resources");
  // 这就到了ThreadLocal流程了 if (map == null) { map = new HashMap<Object, Object>(); resources.set(map); } Object oldValue = map.put(actualKey, value); // Transparently suppress a ResourceHolder that was marked as void... if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) { oldValue = null; } if (oldValue != null) { throw new IllegalStateException("Already value [" + oldValue + "] for key [" + actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]"); } if (logger.isTraceEnabled()) { logger.trace("Bound value [" + value + "] for key [" + actualKey + "] to thread [" + Thread.currentThread().getName() + "]"); } }

总结

  1、ThreadLocal能解决的问题,那肯定不是共享变量(多线程并发)问题,只是看起来有些像并发;像火车票、电影票这样的真正的共享变量的问题用ThreadLocal是解决不了的,同一时间,同一趟车的同一个座位,你敢用ThreadLocal来解决吗?

  2、每个Thread维护一个ThreadLocalMap映射表,这个映射表的key是ThreadLocal实例本身,value是真正需要存储的Object

  3、druid连接池用的是数组来存放的connectionHolder,不是我认为的list,connectionHolder从线程中解绑后,归还到数组连接池中;connectionHolder是connection的封装

疑问

  private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<Map<Object, Object>>("Transactional resources");

  1、 为什么是ThreadLocal<Map<Object, Object>>,而不是ThreadLocal<ConnectionHolder>

  2、 ThreadLocal<Map<Object, Object>> 中的Map的key是为什么是DataSource

  望知道的朋友赐教下,评论留言或者私信都可以,谢谢!

参考

  http://www.cnblogs.com/dolphin0520/p/3920407.html

  http://qifuguang.me/2015/09/02/[Java%E5%B9%B6%E5%8F%91%E5%8C%85%E5%AD%A6%E4%B9%A0%E4%B8%83]%E8%A7%A3%E5%AF%86ThreadLocal/

结合ThreadLocal来看spring事务源码,感受下清泉般的洗涤!的更多相关文章

  1. spring事务源码研读1

    转载摘录自:Spring事务源码分析(一)Spring事务入门 有时为了保证一些操作要么都成功,要么都失败,这就需要事务来保证. 传统的jdbc事务如下: @Test public void test ...

  2. spring事务源码解析

    前言 在spring jdbcTemplate 事务,各种诡异,包你醍醐灌顶!最后遗留了一个问题:spring是怎么样保证事务一致性的? 当然,spring事务内容挺多的,如果都要讲的话要花很长时间, ...

  3. 框架源码系列十一:事务管理(Spring事务管理的特点、事务概念学习、Spring事务使用学习、Spring事务管理API学习、Spring事务源码学习)

    一.Spring事务管理的特点 Spring框架为事务管理提供一套统一的抽象,带来的好处有:1. 跨不同事务API的统一的编程模型,无论你使用的是jdbc.jta.jpa.hibernate.2. 支 ...

  4. Spring事务源码阅读笔记

    1. 背景 本文主要介绍Spring声明式事务的实现原理及源码.对一些工作中的案例与事务源码中的参数进行总结. 2. 基本概念 2.1 基本名词解释 名词 概念 PlatformTransaction ...

  5. spring事务源码分析结合mybatis源码(一)

    最近想提升,苦逼程序猿,想了想还是拿最熟悉,之前也一直想看但没看的spring源码来看吧,正好最近在弄事务这部分的东西,就看了下,同时写下随笔记录下,以备后查. spring tx源码分析 这里只分析 ...

  6. Spring系列(六):Spring事务源码解析

    一.事务概述 1.1 什么是事务 事务是一组原子性的SQL查询,或者说是一个独立的工作单元.要么全部执行,要么全部不执行. 1.2 事务的特性(ACID) ①原子性(atomicity) 一个事务必须 ...

  7. spring事务源码分析结合mybatis源码(三)

    下面将结合mybatis源码来分析下,这种持久化框架是如何对connection使用,来达到spring事务的控制. 想要在把mybatis跟spring整合都需要这样一个jar包:mybatis-s ...

  8. 【面试】足够应付面试的Spring事务源码阅读梳理(建议珍藏)

    Starting from a joke 问:把大象放冰箱里,分几步? 答:三步啊,第一.把冰箱门打开,第二.把大象放进去,第三.把冰箱门带上. 问:实现Spring事务,分几步? 答:三步啊,第一. ...

  9. Spring源码剖析9:Spring事务源码剖析

    转自:http://www.linkedkeeper.com/detail/blog.action?bid=1045 声明式事务使用 Spring事务是我们日常工作中经常使用的一项技术,Spring提 ...

随机推荐

  1. python装饰器通俗易懂的解释!

    1.python装饰器 刚刚接触python的装饰器,简直懵逼了,直接不懂什么意思啊有木有,自己都忘了走了多少遍Debug,查了多少遍资料,猜有点点开始明白了.总结了一下解释得比较好的,通俗易懂的来说 ...

  2. 初识Polymer框架

    什么是polymer? polymer由谷歌的Palm webOS团队打造,并在2013 Google I/O大会上推出,旨在实现Web Components,用最少的代码,解除框架间的限制的UI 框 ...

  3. iOS 标题内容待定

    UITableView: UITableViewCell的声明文件.所包含的: UIView控件(contentView,作为其它元素的父控件) -- 容器 两个UILabel控件( textLabe ...

  4. 简单的哈希表实现 C语言

    简单的哈希表实现 简单的哈希表实现 原理 哈希表和节点数据结构的定义 初始化和释放哈希表 哈希散列算法 辅助函数strDup 哈希表的插入和修改 哈希表中查找 哈希表元素的移除 哈希表打印 测试一下 ...

  5. NGUI之UIPanel

    原文:http://www.tasharen.com/forum/index.php?topic=6705.0 概述 UIPanel用来收集和管理它下面所有widget的组件.通过widget的geo ...

  6. iOS开发笔记5:多线程之NSThread、NSOperation及GCD

    这篇主要总结下iOS开发中多线程的使用,多线程开发一般使用NSThread.NSOperation及GCD三种方式,常用GCD及NSOperation. 1.NSThread 创建线程主要有以下三种方 ...

  7. ASP.NET MVC4 部分视图

    ASP.NET MVC4 部分视图 2014-10-24 16:48 by 易code, 2662 阅读, 1 评论, 收藏, 编辑 [部分视图] ASP.NET MVC 里的部分视图,相当于 Web ...

  8. bzoj2653:middle

    思路:首先容易想到二分答案,但如何去check呢,对于一段区间[l,r],把所有小于答案的都赋值为-1,大于等于它的都赋值为1,然后求左端点在[a,b],右端点在[c,d]的最大子串和即可(也就是区间 ...

  9. iter, yield与enumerate的实现

    模拟实现一个enumerate函数 def myEnumerate(seq, start=0): results = [] n = start for i in seq: results.append ...

  10. krpano 常用标签

    <krpano></krpano>根标签 相当于 <body> <scene></scene>一个全景图场景 <image> 图 ...