在個人博客spring事務源碼解析中,提到了一個很關鍵的點:將connection綁定到當前線程來保證這個線程中的數據庫操做用的是同一個connection。可是沒有細緻的講到如何綁定,以及爲何這麼綁定;另外也沒有講到鏈接池的相關問題:如何從鏈接池獲取,如何歸還鏈接到鏈接池等等。那麼下面就請聽我慢慢道來。html
路漫漫其修遠兮,吾將上下而求索!git
github:https://github.com/youzhibinggithub
碼雲(gitee):https://gitee.com/youzhibingspring
講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(); } }
執行結果以下session
能夠看到,各個線程的longLocal值與stringLocal值是相互獨立的,本線程的累加操做不會影響到其餘線程的值,真正達到了線程內部隔離的效果。多線程
這裏我就不進行ThreadLocal的源碼解析,建議你們去看我參考的博客,我的認爲看那兩篇博客就能對ThreadLocal有個很深地認知了。併發
作個重複的強調(引用[Java併發包學習七]解密ThreadLocal中的一段話):
Thread與ThreadLocal對象之間的引用關係圖
看了ThreadLocal源碼,不知道你們有沒有一個疑惑:爲何像上圖那麼設計? 若是給你設計,你會怎麼設計?相信大部分人會有這樣的想法,我也是這樣的想法: 」每一個ThreadLocal類建立一個Map,而後用線程的ID做爲Map的key,實例對象做爲Map的value,這樣就能達到各個線程的值隔離的效果「 JDK最先期的ThreadLocal就是這樣設計的。(不肯定是不是1.3)以後ThreadLocal的設計換了一種方式,也就是目前的方式,那有什麼優點了: 一、這樣設計以後每一個Map的Entry數量變小了:以前是Thread的數量,如今是ThreadLocal的數量,能提升性能,聽說性能的提高不是一點兩點(沒有親測) 二、當Thread銷燬以後對應的ThreadLocalMap也就隨之銷燬了,能減小內存使用量。
最多見的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() + "]"); } }
一、ThreadLocal能解決的問題,那確定不是共享變量(多線程併發)問題,只是看起來有些像併發;像火車票、電影票這樣的真正的共享變量的問題用ThreadLocal是解決不了的,同一時間,同一趟車的同一個座位,你敢用ThreadLocal來解決嗎?
二、每一個Thread維護一個ThreadLocalMap映射表,這個映射表的key是ThreadLocal實例自己,value是真正須要存儲的Object
三、druid鏈接池用的是數組來存放的connectionHolder,不是我認爲的list,connectionHolder從線程中解綁後,歸還到數組鏈接池中;connectionHolder是connection的封裝
private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<Map<Object, Object>>("Transactional resources");
一、 爲何是ThreadLocal<Map<Object, Object>>,而不是ThreadLocal<ConnectionHolder>
二、 ThreadLocal<Map<Object, Object>> 中的Map的key是爲何是DataSource
望知道的朋友賜教下,評論留言或者私信均可以,謝謝!