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)

  1. public void set(T value) {
  2. Thread t = Thread.currentThread();
  3. ThreadLocalMap map = getMap(t);
  4. if (map != null) {
  5. map.set(this, value);
  6. } else {
  7. createMap(t, value);
  8. }
  9. }
  1. ThreadLocalMap getMap(Thread t) {
  2. return t.threadLocals;
  3. }
  1. void createMap(Thread t, T firstValue) {
  2. t.threadLocals = new ThreadLocalMap(this, firstValue);
  3. }

从源代码可以看出,set 操作先获取当前线程,再获取当前线程的 ThreadLocalMap,如果 map 不为空则把当前 ThreadLocal 对象实例作为key,传进来的value作为值,否则创建一个map,再按照键值对放进去。从这里可以看出,实质上我们最后的存储介质就是这个ThreadLocalMap ,那这个ThreadLocalMap是什么呢?接着往下看。

  1. public class Thread implements Runnable {
  2. ...
  3. ThreadLocal.ThreadLocalMap threadLocals = null;
  4. ...
  5. }
  6. ==================================================================
  7. static class ThreadLocalMap {
  8. static class Entry extends WeakReference<ThreadLocal<?>> {
  9. /** The value associated with this ThreadLocal. */
  10. Object value;
  11. Entry(ThreadLocal<?> k, Object v) {
  12. super(k);
  13. value = v;
  14. }
  15. }
  16. ...
  17. }

每个Thread对象都维护一个ThreadLocalMap 类型的变量 threadLocals ;这个ThreadLocalMapThreadLocal 的一个内部类, ThreadLocalMap 内部维护了一个Entry (键值对),这里可以看到 Entry 继承了 WeakReference ,其对应的构造方法里 ,key值调用了父类的方法,那么意味着Entry 所对应的key(ThreadLocal对象实例)的引用是一个弱引用。

WeakReference 是java四种引用用中的弱引用,当有gc发生时就会被回收。 还有其他三种分别是强引用,虚引用,软引用。

那为什么这里设置成弱引用呢?主要是为了防止内存泄漏,下面我们来分析一下。

image.png
假设我们在方法内部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()

  1. public void remove() {
  2. ThreadLocalMap m = getMap(Thread.currentThread());
  3. if (m != null) {
  4. m.remove(this);
  5. }
  6. }

remove 操作还是比较简单的,就是通过当前Thread 对象获取 ThreadLocalMap ,若不为空则再根据 ThreadLocal 对象作为key删除value。

get()

  1. public T get() {
  2. Thread t = Thread.currentThread();
  3. ThreadLocalMap map = getMap(t);
  4. if (map != null) {
  5. ThreadLocalMap.Entry e = map.getEntry(this);
  6. if (e != null) {
  7. @SuppressWarnings("unchecked")
  8. T result = (T)e.value;
  9. return result;
  10. }
  11. }
  12. return setInitialValue();
  13. }
  1. private T setInitialValue() {
  2. T value = initialValue();
  3. Thread t = Thread.currentThread();
  4. ThreadLocalMap map = getMap(t);
  5. if (map != null) {
  6. map.set(this, value);
  7. } else {
  8. createMap(t, value);
  9. }
  10. if (this instanceof TerminatingThreadLocal) {
  11. TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
  12. }
  13. return value;
  14. }

接着我们来看下get 操作,get 操作同样也是先获取当前ThreadThreadLocalMap,再根据ThreadLocal对象获取对应的Entry,最后获取值。若map为空,则初始化值然后将初始化的值返回。

应用实践

Spring事务管理

在Web项目编程中,我们都会与数据库进行打交道,往往通常的做法是一个Service层里包含了多个Dao层的操作,要保证Service层操作的原子性,就要保证这些Dao操作是在同一个事务里,在同一个事务里就要确保多个Dao层的操作都是同一个Connection,那如何保证呢?我们可以确定的是该多个Dao层的操作都是由相同的线程进行处理的,那只要把Connection与线程绑定就可以了,所以Spring这里就巧妙的使用ThreadLocal来解决了这个问题。
Spring中有一个类 DataSourceUtils 其中有方法是获取数据源的Connection的,里面有一个getConnection方法,如下所示。

  1. public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
  2. try {
  3. return doGetConnection(dataSource);
  4. }
  5. catch (SQLException ex) {
  6. throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection", ex);
  7. }
  8. catch (IllegalStateException ex) {
  9. throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection: " + ex.getMessage());
  10. }
  11. }

for example when using {@link DataSourceTransactionManager}. Will bind a Connection to the thread if transaction synchronization is active 这里的注释有一个细节要关注到就是注释中提及到 如果使用数据源事务管理器,当开启事务时,那么就会绑定连接到当前线程。

  1. /**
  2. * Actually obtain a JDBC Connection from the given DataSource.
  3. * Same as {@link #getConnection}, but throwing the original SQLException.
  4. * <p>Is aware of a corresponding Connection bound to the current thread, for example
  5. * when using {@link DataSourceTransactionManager}. Will bind a Connection to the thread
  6. * if transaction synchronization is active (e.g. if in a JTA transaction).
  7. * <p>Directly accessed by {@link TransactionAwareDataSourceProxy}.
  8. * @param dataSource the DataSource to obtain Connections from
  9. * @return a JDBC Connection from the given DataSource
  10. * @throws SQLException if thrown by JDBC methods
  11. * @see #doReleaseConnection
  12. */
  13. public static Connection doGetConnection(DataSource dataSource) throws SQLException {
  14. Assert.notNull(dataSource, "No DataSource specified");
  15. ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
  16. if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
  17. conHolder.requested();
  18. if (!conHolder.hasConnection()) {
  19. logger.debug("Fetching resumed JDBC Connection from DataSource");
  20. conHolder.setConnection(fetchConnection(dataSource));
  21. }
  22. return conHolder.getConnection();
  23. }
  24. // Else we either got no holder or an empty thread-bound holder here.
  25. logger.debug("Fetching JDBC Connection from DataSource");
  26. Connection con = fetchConnection(dataSource);
  27. if (TransactionSynchronizationManager.isSynchronizationActive()) {
  28. try {
  29. // Use same Connection for further JDBC actions within the transaction.
  30. // Thread-bound object will get removed by synchronization at transaction completion.
  31. ConnectionHolder holderToUse = conHolder;
  32. if (holderToUse == null) {
  33. holderToUse = new ConnectionHolder(con);
  34. }
  35. else {
  36. holderToUse.setConnection(con);
  37. }
  38. holderToUse.requested();
  39. TransactionSynchronizationManager.registerSynchronization(
  40. new ConnectionSynchronization(holderToUse, dataSource));
  41. holderToUse.setSynchronizedWithTransaction(true);
  42. if (holderToUse != conHolder) {
  43. TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
  44. }
  45. }
  46. catch (RuntimeException ex) {
  47. // Unexpected exception from external delegation call -> close Connection and rethrow.
  48. releaseConnection(con, dataSource);
  49. throw ex;
  50. }
  51. }
  52. return con;
  53. }

从源代码大致可以看出首先从TransactionSynchronizationManager中获取ConnectionHolder,若存在则直接返回Connection,若不存在则新生成一个Connection并装到ConnectionHolder中然后注册到TransactionSynchronizationManager 中,然后再返回Connection。由此,我们可以看出TransactionSynchronizationManager 在这其中起到了管理Connection的作用。

接着看下TransactionSynchronizationManager 类。其中getResource方法和bindResource方法都在上面的doGetConnection方法中有过调用,那我们就注重看下这几个方法。

  1. public abstract class TransactionSynchronizationManager {
  2. private static final ThreadLocal<Map<Object, Object>> resources =
  3. new NamedThreadLocal<>("Transactional resources");
  4. private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
  5. new NamedThreadLocal<>("Transaction synchronizations");
  6. private static final ThreadLocal<String> currentTransactionName =
  7. new NamedThreadLocal<>("Current transaction name");
  8. private static final ThreadLocal<Boolean> currentTransactionReadOnly =
  9. new NamedThreadLocal<>("Current transaction read-only status");
  10. private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
  11. new NamedThreadLocal<>("Current transaction isolation level");
  12. private static final ThreadLocal<Boolean> actualTransactionActive =
  13. new NamedThreadLocal<>("Actual transaction active");
  14. ...
  15. public static Object getResource(Object key) {
  16. Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
  17. Object value = doGetResource(actualKey);
  18. if (value != null && logger.isTraceEnabled()) {
  19. logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" +
  20. Thread.currentThread().getName() + "]");
  21. }
  22. return value;
  23. }
  24. private static Object doGetResource(Object actualKey) {
  25. Map<Object, Object> map = resources.get();
  26. if (map == null) {
  27. return null;
  28. }
  29. Object value = map.get(actualKey);
  30. // Transparently remove ResourceHolder that was marked as void...
  31. if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
  32. map.remove(actualKey);
  33. // Remove entire ThreadLocal if empty...
  34. if (map.isEmpty()) {
  35. resources.remove();
  36. }
  37. value = null;
  38. }
  39. return value;
  40. }
  41. ...
  42. public static void bindResource(Object key, Object value) throws IllegalStateException {
  43. Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
  44. Assert.notNull(value, "Value must not be null");
  45. Map<Object, Object> map = resources.get();
  46. // set ThreadLocal Map if none found
  47. if (map == null) {
  48. map = new HashMap<>();
  49. resources.set(map);
  50. }
  51. Object oldValue = map.put(actualKey, value);
  52. // Transparently suppress a ResourceHolder that was marked as void...
  53. if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {
  54. oldValue = null;
  55. }
  56. if (oldValue != null) {
  57. throw new IllegalStateException("Already value [" + oldValue + "] for key [" +
  58. actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
  59. }
  60. if (logger.isTraceEnabled()) {
  61. logger.trace("Bound value [" + value + "] for key [" + actualKey + "] to thread [" +
  62. Thread.currentThread().getName() + "]");
  63. }
  64. }

从以上代码可以得出TransactionSynchronizationManager 类维护了ThreadLocal对象来进行资源的存储,包括事务资源(Spring中对JDBC的Connection或Hibernate的Session都称之为资源),事务隔离级别等。
名为resources变量的ThreadLocal对象存储的是DataSource生成的actualKey为key值和ConnectionHolder作为value值封装成的Map。
再结合DataSourceUtilsdoGetConnection方法和TransactionSynchronizationManagerbindResourcegetResource方法可知:在某个线程第一次调用时候,封装Map资源为:key值为DataSource生成actualKey和value值为DataSource获得的Connection对象封装后的ConnectionHolder。等这个线程下一次再次访问中就能保证使用的是第一次创建的ConnectionHolder中的Connection对象。

参考链接

【死磕Java并发】—–深入分析ThreadLocal
Spring事务之如何保证同一个Connection对象
Spring是如何保证同一事务获取同一个Connection的?使用Spring的事务同步机制解决:数据库刚插入的记录却查询不到的问题【享学Spring】