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 foundif (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】
