ThreadLocal
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its {@code get} or {@code set} method) has its own, independently initialized copy of the variable. {@code ThreadLocal} instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).
上文节选自ThreadLocal
的 JavaDoc,从形容的内容上看咱们就能够看出 ThreadLocal
的作用是提供线程局部变量,这些局部变量是原变量的正本;ThreadLocal
是为每个线程提供一份变量的正本,由此不会影响到其余线程的变量正本。
源码浅析
咱们先来看看 ThreadLocal
下反对的几个罕用操作。set(T value)
,get()
,remove()
;
set(T value)
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { map.set(this, value); } else { createMap(t, value); }}
ThreadLocalMap getMap(Thread t) { return t.threadLocals;}
void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue);}
从源代码能够看出,set
操作先获取以后线程,再获取以后线程的 ThreadLocalMap
,如果 map
不为空则把以后 ThreadLocal
对象实例作为key,传进来的value作为值,否则创立一个map,再依照键值对放进去。从这里能够看出,本质上咱们最初的存储介质就是这个ThreadLocalMap
,那这个ThreadLocalMap
是什么呢?接着往下看。
public class Thread implements Runnable { ... ThreadLocal.ThreadLocalMap threadLocals = null; ... }==================================================================static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }... }
每个Thread
对象都保护一个ThreadLocalMap
类型的变量 threadLocals
;这个ThreadLocalMap
是 ThreadLocal
的一个外部类, ThreadLocalMap
外部保护了一个Entry
(键值对),这里能够看到 Entry
继承了 WeakReference
,其对应的构造方法里 ,key值调用了父类的办法,那么意味着Entry
所对应的key(ThreadLocal对象实例)的援用是一个弱援用。
WeakReference
是java四种援用用中的弱援用,当有gc产生时就会被回收。
还有其余三种别离是强援用,虚援用,软援用。
那为什么这里设置成弱援用呢?次要是为了避免内存透露,上面咱们来剖析一下。
假如咱们在办法外部new
了一个ThreadLocal
对象,并往里面set
值。此时堆内存中的 ThreadLocal
对象有两块援用指向它,第一个援用是栈内存的强援用;另外一个是当set
值之后Entry
的key作为的弱援用。办法完结时,当指向 ThreadLocal
对象的强援用隐没时,对应的弱援用也会主动被回收。
咱们假如Entry
中的援用是强援用,当指向ThreadLocal
对象的强援用隐没时,ThreadLocal
对象应该被回收但却因为Entry
中的强援用无奈回收,咱们晓得 ThreadlocalMap
是属于Thread
的,如果是服务端线程的话,因为Thread
长期存在,ThreadLocalMap
也必然长期存在,那么对应的这个Entry
也会长期存在,那这个ThreadLocal
对象就不会被回收,就造成了内存透露。所以这就是为什么要应用弱援用的起因。
除此之外,JDK的设计者曾经帮咱们应用弱援用来防止了内存透露,认真想想这样就不会再产生内存透露了吗?当对应的Entry
中的key被回收,那这个value是不是就无奈获取到了呢,但因为Thread
长期存在,ThreadLocalMap
也长期存在,Entry
也会长期存在,value也会永远都无奈开释了,这样还是会造成内存透露。所以,在每次应用完之后,都须要调用remove
办法进行资源革除。
说到这里,回忆一下如果在拦截器里应用ThreadLocal,就能了解为什么在afterCompletion
须要remove
办法了吧,如果不进行资源革除,就会导致线程在第二次申请中get
到第一次申请的set
进去的值。
remove()
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) { m.remove(this); }}
remove
操作还是比较简单的,就是通过以后Thread
对象获取 ThreadLocalMap
,若不为空则再依据 ThreadLocal
对象作为key删除value。
get()
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue();}
private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { map.set(this, value); } else { createMap(t, value); } if (this instanceof TerminatingThreadLocal) { TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this); } return value;}
接着咱们来看下get
操作,get
操作同样也是先获取以后Thread
的ThreadLocalMap
,再依据ThreadLocal
对象获取对应的Entry
,最初获取值。若map为空,则初始化值而后将初始化的值返回。
利用实际
Spring事务管理
在Web我的项目编程中,咱们都会与数据库进行打交道,往往通常的做法是一个Service层里蕴含了多个Dao层的操作,要保障Service层操作的原子性,就要保障这些Dao操作是在同一个事务里,在同一个事务里就要确保多个Dao层的操作都是同一个Connection,那如何保障呢?咱们能够确定的是该多个Dao层的操作都是由雷同的线程进行解决的,那只有把Connection与线程绑定就能够了,所以Spring这里就奇妙的应用ThreadLocal
来解决了这个问题。
Spring中有一个类 DataSourceUtils
其中有办法是获取数据源的Connection的,外面有一个getConnection
办法,如下所示。
public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException { try { return doGetConnection(dataSource); } catch (SQLException ex) { throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection", ex); } catch (IllegalStateException ex) { throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection: " + ex.getMessage()); }}
for example when using {@link DataSourceTransactionManager}. Will bind a Connection to the
thread if transaction synchronization is active
这里的正文有一个细节要关注到就是正文中提及到 如果应用数据源事务管理器,当开启事务时,那么就会绑定连贯到以后线程。
/*** Actually obtain a JDBC Connection from the given DataSource.* Same as {@link #getConnection}, but throwing the original SQLException.* <p>Is aware of a corresponding Connection bound to the current thread, for example* when using {@link DataSourceTransactionManager}. Will bind a Connection to the thread* if transaction synchronization is active (e.g. if in a JTA transaction).* <p>Directly accessed by {@link TransactionAwareDataSourceProxy}.* @param dataSource the DataSource to obtain Connections from* @return a JDBC Connection from the given DataSource* @throws SQLException if thrown by JDBC methods* @see #doReleaseConnection*/public static Connection doGetConnection(DataSource dataSource) throws SQLException { Assert.notNull(dataSource, "No DataSource specified"); ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource); if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) { conHolder.requested(); if (!conHolder.hasConnection()) { logger.debug("Fetching resumed JDBC Connection from DataSource"); conHolder.setConnection(fetchConnection(dataSource)); } 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 = fetchConnection(dataSource); if (TransactionSynchronizationManager.isSynchronizationActive()) { try { // 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); } } catch (RuntimeException ex) { // Unexpected exception from external delegation call -> close Connection and rethrow. releaseConnection(con, dataSource); throw ex; } } return con;}
从源代码大抵能够看出首先从TransactionSynchronizationManager
中获取ConnectionHolder
,若存在则间接返回Connection
,若不存在则新生成一个Connection
并装到ConnectionHolder
中而后注册到TransactionSynchronizationManager
中,而后再返回Connection
。由此,咱们能够看出TransactionSynchronizationManager
在这其中起到了治理Connection
的作用。
接着看下TransactionSynchronizationManager
类。其中getResource
办法和bindResource
办法都在下面的doGetConnection
办法中有过调用,那咱们就重视看下这几个办法。
public abstract class TransactionSynchronizationManager {private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources"); private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal<>("Transaction synchronizations"); private static final ThreadLocal<String> currentTransactionName = new NamedThreadLocal<>("Current transaction name"); private static final ThreadLocal<Boolean> currentTransactionReadOnly = new NamedThreadLocal<>("Current transaction read-only status"); private static final ThreadLocal<Integer> currentTransactionIsolationLevel = new NamedThreadLocal<>("Current transaction isolation level"); private static final ThreadLocal<Boolean> actualTransactionActive = new NamedThreadLocal<>("Actual transaction active");...public static Object getResource(Object key) { Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key); Object value = doGetResource(actualKey); if (value != null && logger.isTraceEnabled()) { logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]"); } return value;} private static Object doGetResource(Object actualKey) { Map<Object, Object> map = resources.get(); if (map == null) { return null; } Object value = map.get(actualKey); // Transparently remove ResourceHolder that was marked as void... if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) { map.remove(actualKey); // Remove entire ThreadLocal if empty... if (map.isEmpty()) { resources.remove(); } value = null; } return value;}...public static void bindResource(Object key, Object value) throws IllegalStateException { 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 if (map == null) { map = new HashMap<>(); 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() + "]"); }}
从以上代码能够得出TransactionSynchronizationManager
类保护了ThreadLocal
对象来进行资源的存储,包含事务资源(Spring中对JDBC的Connection或Hibernate的Session都称之为资源),事务隔离级别等。
名为resources变量的ThreadLocal
对象存储的是DataSource生成的actualKey为key值和ConnectionHolder作为value值封装成的Map。
再联合DataSourceUtils
的doGetConnection
办法和TransactionSynchronizationManager
的bindResource
和getResource
办法可知:在某个线程第一次调用时候,封装Map资源为:key值为DataSource生成actualKey和value值为DataSource取得的Connection
对象封装后的ConnectionHolder
。等这个线程下一次再次拜访中就能保障应用的是第一次创立的ConnectionHolder
中的Connection
对象。
参考链接
【死磕Java并发】—–深入分析ThreadLocal
Spring事务之如何保障同一个Connection对象
Spring是如何保障同一事务获取同一个Connection的?应用Spring的事务同步机制解决:数据库刚插入的记录却查问不到的问题【享学Spring】