ThreadLocal 是 Java 并发编程中实现**线程封闭(Thread Confinement)**的核心工具。本文将从原理到实践,系统性地讲解 ThreadLocal 的设计哲学、内部机制、使用模式以及跨线程传递方案。
原理篇:ThreadLocal 的内部机制
核心设计理念:为什么不用 Map<Thread, Value>?
很多人初次设计线程本地存储时,会想到用一个全局的 Map<Thread, Value> 来存储每个线程的数据。但这种设计有致命缺陷:Thread 对象会被 Map 强引用,导致线程无法被 JVM 回收,造成严重的内存泄漏。
ThreadLocal 采用了相反的设计:让 Thread 持有 Map,而不是让 Map 持有 Thread 。每个 Thread 内部都有一个 ThreadLocalMap,用于存储该线程的所有线程本地变量。这样设计的好处是:
线程销毁时,ThreadLocalMap 随之销毁,数据自动清理
ThreadLocal 对象可以被显式管理(如声明为静态变量)
线程内部的存储容器是隐式的,由线程自己管理
引用关系结构
1 2 3 4 5 Thread -> ThreadLocalMap -> Entry[] ↓ Entry extends WeakReference<ThreadLocal<?>> - key: ThreadLocal 对象(弱引用) - value: 实际存储的值(强引用)
一个 ThreadLocal 变量的"副本"实际上是分散存储在多个线程的 ThreadLocalMap 中的。每个线程的 ThreadLocalMap 中都有一个 Entry,以同一个 ThreadLocal 对象为 key,但 value 是各自独立的。
静态变量场景的引用关系:
1 2 方法区静态变量 -> 强引用 ThreadLocal 对象 ThreadLocalMap.Entry -> 弱引用 ThreadLocal 对象
四大核心原则
原则1:操作的本质
当我们调用 ThreadLocal.get() 或 ThreadLocal.set() 时,实际上是在操作当前线程内部的 ThreadLocalMap 。ThreadLocal 对象本身只是一个"访问入口",真正的数据存储在各个线程的隐藏 Map 中。
原则2:ThreadLocal 应该是 static 变量
ThreadLocal 应该声明为 static 变量,作为类级别的全局唯一实例。原因有三:
避免内存浪费 :如果作为成员变量,每个对象实例都会创建新的 ThreadLocal,导致每个线程的 ThreadLocalMap 中会有多个 Entry,浪费内存且违背线程本地存储的设计初衷。我们的初衷是每个线程有一个变量的副本,而不是多个副本 。
防止对象无法回收 :如果 ThreadLocal 是成员变量,开发者会习惯性地通过 对象实例.threadLocal.get() 来访问 ThreadLocal。这种使用方式会导致对象实例被外部持有引用,进而无法被 GC 回收。而如果 ThreadLocal 是 static 变量,访问方式是 类.threadLocal.get(),不会持有对象实例的引用,避免了对象级别的内存泄漏。
Class 生命周期更长 :相比之下,Class 对象通常不需要频繁回收,其生命周期与应用程序相当,因此 static 成员是可以接受的。static 声明确保全局只有一个 ThreadLocal 实例,每个线程的 Map 中只有一个对应的 Entry,既节省内存又避免了对象无法回收的问题。
原则3:线程生命周期决定数据生命周期
即使我们不主动调用 remove(),当线程销毁时(如普通线程执行完毕),该线程的 ThreadLocalMap 也会随之销毁,所有 value 自动被回收。这就是为什么在非线程池场景下,ThreadLocal 的内存泄漏问题不那么严重。
原则4:ThreadLocal 对象的生命周期影响所有线程
如果我们将 ThreadLocal 静态变量置为 null(去除强引用),那么所有线程的 ThreadLocalMap 中对应的 Entry 的 key 都会失效(弱引用被回收)。即使线程什么都不做,只要后续有任何 get/set/remove 操作触发,这些 key 为 null 的 Entry 就会被自动清理,value 随之消失。
为什么 key 使用弱引用?
因为线程内部的 ThreadLocalMap 是隐式容器,由线程自己管理。如果 key 使用强引用,那么只要线程存活(如线程池场景),ThreadLocal 对象就永远无法被回收。使用弱引用后,当 ThreadLocal 对象没有外部强引用时(如静态变量被置为 null),它可以被 GC 回收,Entry 的 key 变为 null,后续的 get/set/remove 操作会自动清理这些过期的 Entry。
弱引用的特性 :在垃圾回收器线程扫描内存区域时,一旦发现只具有弱引用的对象,不管当前内存空间是否足够,都会回收它的内存。
Stale Entry 的自动清理机制
当 ThreadLocal 对象失去强引用后,GC 会回收它,此时 Entry 中的弱引用 key 会变成 null。但 Entry 本身仍然占据 ThreadLocalMap 的槽位,value 也仍然被 Entry 强引用。这种 key 为 null 的 Entry 被称为 Stale Entry(过期条目) 。
ThreadLocalMap 的 get()、set()、remove() 方法在执行过程中,会主动检测并清理 这些 Stale Entry。这是通过调用 expungeStaleEntry() 方法实现的:
为什么 get/set/remove 能"知道" Entry 已过期?
这是弱引用的核心特性:WeakReference.get() 方法会返回被引用的对象,但如果该对象已被 GC 回收,则返回 null。ThreadLocalMap 的 Entry 继承自 WeakReference<ThreadLocal<?>>,因此:
当 ThreadLocal 对象存活时:entry.get() 返回 ThreadLocal 对象。
当 ThreadLocal 对象被 GC 回收后:entry.get() 返回 null——这就是继承 WeakReference 的好处。
清理的时机与局限性:
操作
是否触发清理
清理范围
get()
是
遍历过程中遇到的 Stale Entry
set()
是
遍历过程中遇到的 Stale Entry + 可能触发全表扫描
remove()
是
遍历过程中遇到的 Stale Entry
无任何操作
否
这就是泄漏发生的根本原因
关键结论 :自动清理机制是被动触发 的,只有在调用 get/set/remove 时才会执行。如果线程长期存活(如线程池)且不再访问任何 ThreadLocal,那些 Stale Entry 将永远不会被清理,导致内存泄漏。
关键源码解析(JDK 8):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 private Entry getEntry (ThreadLocal<?> key) { int i = key.threadLocalHashCode & (table.length - 1 ); Entry e = table[i]; if (e != null && e.get() == key) return e; else return getEntryAfterMiss(key, i, e); }private int expungeStaleEntry (int staleSlot) { Entry[] tab = table; int len = tab.length; tab[staleSlot].value = null ; tab[staleSlot] = null ; size--; }
ThreadLocal 核心方法调用链解析
理解 ThreadLocal 的工作原理,关键在于理清各个方法之间的调用关系。下面我们从源码层面逐一分析。
方法调用关系图
核心方法源码解析
1. ThreadLocal.set(T value) —— 设置入口
1 2 3 4 5 6 7 8 9 10 11 12 public void set (T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null ) map.set(this , value); else createMap(t, value); }
设计要点 :set() 方法体现了懒加载 思想——只有在首次调用 set() 时才创建 ThreadLocalMap。
2. getMap(Thread t) —— 获取线程的 Map
1 2 3 ThreadLocalMap getMap (Thread t) { return t.threadLocals; }
设计要点 :这个方法揭示了 ThreadLocal 的核心设计——Map 存储在 Thread 对象内部 ,而不是 ThreadLocal 对象内部。这是"让 Thread 持有 Map,而不是让 Map 持有 Thread"设计理念的直接体现。
3. createMap(Thread t, T firstValue) —— 创建 Map
1 2 3 void createMap (Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap (this , firstValue); }
设计要点 :创建 Map 时直接传入第一个键值对,避免了"先创建空 Map,再插入"的两步操作。
4. ThreadLocalMap 构造函数
1 2 3 4 5 6 7 8 9 10 11 ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { table = new Entry [INITIAL_CAPACITY]; int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1 ); table[i] = new Entry (firstKey, firstValue); size = 1 ; setThreshold(INITIAL_CAPACITY); }
设计要点 :
初始容量 16,与 HashMap 相同
使用 0x61c88647(黄金分割数)作为哈希增量,使 Entry 分布更均匀
扩容阈值为容量的 2/3,比 HashMap 的 0.75 更保守,减少哈希冲突
5. ThreadLocalMap.set(ThreadLocal<?> key, Object value) —— 核心设置逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 private void set (ThreadLocal<?> key, Object value) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len - 1 ); for (Entry e = tab[i]; e != null ; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); if (k == key) { e.value = value; return ; } if (k == null ) { replaceStaleEntry(key, value, i); return ; } } tab[i] = new Entry (key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
设计要点 :
使用线性探测 解决哈希冲突
在探测过程中顺便清理 Stale Entry(k == null 的情况)
cleanSomeSlots() 是启发式清理,不会扫描全表
6. ThreadLocal.get() —— 获取入口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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(); }
7. getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) —— 哈希冲突时的查找
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private Entry getEntryAfterMiss (ThreadLocal<?> key, int i, Entry e) { Entry[] tab = table; int len = tab.length; while (e != null ) { ThreadLocal<?> k = e.get(); if (k == key) return e; if (k == null ) expungeStaleEntry(i); else i = nextIndex(i, len); e = tab[i]; } return null ; }
设计要点 :在查找过程中顺便清理 遇到的 Stale Entry,这是"惰性清理"策略的体现。
8. ThreadLocal.remove() 和 ThreadLocalMap.remove(ThreadLocal<?> key)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public void remove () { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null ) m.remove(this ); }private void remove (ThreadLocal<?> key) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len - 1 ); for (Entry e = tab[i]; e != null ; e = tab[i = nextIndex(i, len)]) { if (e.get() == key) { e.clear(); expungeStaleEntry(i); return ; } } }
9. expungeStaleEntry(int staleSlot) —— 核心清理方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 private int expungeStaleEntry (int staleSlot) { Entry[] tab = table; int len = tab.length; tab[staleSlot].value = null ; tab[staleSlot] = null ; size--; Entry e; int i; for (i = nextIndex(staleSlot, len); (e = tab[i]) != null ; i = nextIndex(i, len)) { ThreadLocal<?> k = e.get(); if (k == null ) { e.value = null ; tab[i] = null ; size--; } else { int h = k.threadLocalHashCode & (len - 1 ); if (h != i) { tab[i] = null ; while (tab[h] != null ) h = nextIndex(h, len); tab[h] = e; } } } return i; }
设计要点 :
参数 staleSlot 是已知的 Stale Entry 位置
不仅清理目标位置,还会继续向后扫描 清理更多 Stale Entry
对有效 Entry 进行 rehash ,确保线性探测链不断裂
返回值是扫描结束的位置,供调用者使用
方法调用关系总结
外部 API
调用的内部方法
可能触发的清理方法
set(T)
getMap() → ThreadLocalMap.set() 或 createMap()
replaceStaleEntry() → expungeStaleEntry()cleanSomeSlots() → expungeStaleEntry()
get()
getMap() → getEntry() → getEntryAfterMiss() 或 setInitialValue()
expungeStaleEntry()
remove()
getMap() → ThreadLocalMap.remove()
expungeStaleEntry()
核心结论 :expungeStaleEntry() 是所有清理操作的最终执行者 ,而 get()、set()、remove() 都会在执行过程中触发它,实现"惰性清理"。
为什么 ThreadLocalMap 使用开放地址法而不是链表法?
HashMap 使用"数组 + 链表/红黑树"的结构来处理哈希冲突,而 ThreadLocalMap 却选择了开放地址法(线性探测) 。这个设计选择背后有深刻的考量:
对比维度
HashMap(链表法)
ThreadLocalMap(开放地址法)
冲突处理
冲突的元素挂在同一个桶的链表上
冲突时向后探测下一个空槽位
内存布局
链表节点分散在堆中,缓存不友好
所有 Entry 在连续数组中,缓存友好
空间开销
每个节点需要额外的 next 指针
无额外指针开销
删除操作
简单的链表节点删除
需要 rehash 后续元素(复杂)
ThreadLocalMap 选择开放地址法的核心原因:
Entry 数量通常很少 :一个线程的 ThreadLocalMap 中通常只有几个到几十个 Entry(对应几个 ThreadLocal 变量),远少于 HashMap 的典型使用场景。在元素少的情况下,开放地址法的线性探测效率很高。
弱引用清理的需要 :ThreadLocalMap 的 key 是弱引用,需要在遍历过程中发现并清理 Stale Entry。开放地址法的线性探测天然支持这种"顺便清理"的模式——在查找目标 Entry 的过程中,可以顺便清理沿途遇到的 Stale Entry。
缓存友好性 :开放地址法将所有 Entry 存储在连续的数组中,CPU 缓存预取效果好。对于频繁访问的 ThreadLocal(如每次请求都要读取的上下文信息),缓存友好性带来的性能提升是显著的。
内存泄漏的发生机制
ThreadLocal 的内存泄漏实际上是一个条件链,任何一个环节被破坏都可能导致泄漏:
ThreadLocal 对象被回收 :当 ThreadLocal 对象没有强引用时(如静态变量被置为 null),它会被 GC 回收
Entry 的 key 变为 null :ThreadLocalMap 中对应的 Entry 的 key(弱引用)失效
value 仍被强引用 :但 value 仍被 Entry 强引用,无法被回收(value 不是被 key 引用,而是被 Entry 引用 )
自动清理机制 :后续的 get/set/remove 操作会触发 expungeStaleEntry(),清理 key 为 null 的 Entry
泄漏发生 :如果线程长期存活(如线程池)且不再调用 get/set/remove,这些 Entry 永远不会被清理
Value 泄漏的因果链 :
1 2 3 4 5 ThreadLocal 对象失去强引用 → Entry .key (WeakReference) 被 GC 回收变成 null → 但 Entry 本身还在 ThreadLocalMap 中(Entry 泄漏/Stale Entry ) → Entry 持有 value 的强引用 → value 无法被回收(Value 泄漏)
结论 :Value 泄漏是 Entry 泄漏的直接后果。更准确地说,是因为 Stale Entry 没有被及时清理 ——ThreadLocal 对象本身是可以被回收的(因为是弱引用),问题在于回收后遗留的 Stale Entry 没有被清理。
实践篇:ThreadLocal 的使用模式
基础版本:静态工具类封装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public class ServerContext { private String traceId; private String userId; }public class ContextHolder { private static final ThreadLocal<ServerContext> SERVER_CONTEXT = new ThreadLocal <>(); public static ServerContext getServerContext () { return SERVER_CONTEXT.get(); } public static void setServerContext (ServerContext context) { SERVER_CONTEXT.set(context); } public static void clear () { SERVER_CONTEXT.remove(); } }public class BizContext { public static void setCurrentServerContext (final ServerContext context) { if (context == null ) { ContextHolder.clear(); } else { ContextHolder.setServerContext(context); } } }
用 Map 来取消第一层工具类的方案
这种方案使用一个 Map 来存储多种类型的上下文,但Map 容易腐化 ,需要谨慎使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 public class ContextFactory { protected ContextFactory () { } private static final ThreadLocal<Map<String, Object>> CONTEXT_HOLDER = InheritableThreadLocal.withInitial(() -> new ConcurrentHashMap <>(16 )); public static void clear () { CONTEXT_HOLDER.remove(); } public static ThreadLocal<Map<String, Object>> getContextHolder () { return CONTEXT_HOLDER; } }public class TransactionContextFactory extends ContextFactory { private TransactionContextFactory () { throw new UnsupportedOperationException (); } private static final String TRANSACTION_CONTEXT_KEY = "TransactionContext" ; @SuppressWarnings("unchecked") public static <T> TransactionContext<T> getTransactionContext () { Map<String, Object> realContextHolder = getContextHolder().get(); if (null == realContextHolder) { realContextHolder = new ConcurrentHashMap <>(16 ); getContextHolder().set(realContextHolder); } TransactionContext<T> realContext; Object mapValue = realContextHolder.get(TRANSACTION_CONTEXT_KEY); if (mapValue instanceof TransactionContext) { realContext = (TransactionContext<T>) mapValue; } else { realContext = new TransactionContext <>(); realContextHolder.put(TRANSACTION_CONTEXT_KEY, realContext); } return realContext; } }
对这个 Map 的加强版本——不可变 Map 模式 :
1 2 3 4 5 6 7 8 9 10 11 @Override public void put (final String key, final String value) { if (!useMap) { return ; } Map<String, String> map = localMap.get(); map = map == null ? new HashMap <>(1 ) : new HashMap <>(map); map.put(key, value); localMap.set(Collections.unmodifiableMap(map)); }
绑定容器到线程并保存上一个状态
这是 Spring 事务管理中使用的经典模式——栈式上下文管理 :
1 2 3 4 5 6 7 8 9 10 11 private void bindToThread () { this .oldTransactionInfo = transactionInfoHolder.get(); transactionInfoHolder.set(this ); }private void restoreThreadLocalStatus () { transactionInfoHolder.set(this .oldTransactionInfo); }
ThreadLocal 变策略模式
Spring Security 的 SecurityContextHolder 是一个经典的策略模式实现,支持三种存储策略:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 public interface SecurityContextHolderStrategy { void clearContext () ; SecurityContext getContext () ; void setContext (SecurityContext context) ; SecurityContext createEmptyContext () ; }final class InheritableThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy { private static final ThreadLocal<SecurityContext> contextHolder = new InheritableThreadLocal <>(); public void clearContext () { contextHolder.remove(); } public SecurityContext getContext () { SecurityContext ctx = contextHolder.get(); if (ctx == null ) { ctx = createEmptyContext(); contextHolder.set(ctx); } return ctx; } public void setContext (SecurityContext context) { Assert.notNull(context, "Only non-null SecurityContext instances are permitted" ); contextHolder.set(context); } public SecurityContext createEmptyContext () { return new SecurityContextImpl (); } }public class SecurityContextHolder { public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL" ; public static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL" ; public static final String MODE_GLOBAL = "MODE_GLOBAL" ; private static String strategyName = System.getProperty("spring.security.strategy" ); private static SecurityContextHolderStrategy strategy; static { initialize(); } private static void initialize () { if (!StringUtils.hasText(strategyName)) { strategyName = MODE_THREADLOCAL; } if (strategyName.equals(MODE_THREADLOCAL)) { strategy = new ThreadLocalSecurityContextHolderStrategy (); } else if (strategyName.equals(MODE_INHERITABLETHREADLOCAL)) { strategy = new InheritableThreadLocalSecurityContextHolderStrategy (); } else if (strategyName.equals(MODE_GLOBAL)) { strategy = new GlobalSecurityContextHolderStrategy (); } else { try { Class<?> clazz = Class.forName(strategyName); Constructor<?> customStrategy = clazz.getConstructor(); strategy = (SecurityContextHolderStrategy) customStrategy.newInstance(); } catch (Exception ex) { ReflectionUtils.handleReflectionException(ex); } } } public static void clearContext () { strategy.clearContext(); } public static SecurityContext getContext () { return strategy.getContext(); } public static void setContext (SecurityContext context) { strategy.setContext(context); } }
带名字的 ThreadLocal
Spring 提供的 NamedThreadLocal,便于调试和诊断:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class NamedThreadLocal <T> extends ThreadLocal <T> { private final String name; public NamedThreadLocal (String name) { Assert.hasText(name, "Name must not be empty" ); this .name = name; } @Override public String toString () { return this .name; } }
跨线程传递篇:InheritableThreadLocal 与 TransmittableThreadLocal
InheritableThreadLocal 的工作原理
Thread 类的双 Map 设计
Thread 类里面其实存在两个 ThreadLocalMap:
1 2 3 4 5 6 7 8 public class Thread implements Runnable { ThreadLocal.ThreadLocalMap threadLocals = null ; ThreadLocal.ThreadLocalMap inheritableThreadLocals = null ; }
InheritableThreadLocal 的极简实现
令人惊讶的是,InheritableThreadLocal 只有 3 个方法 ,却实现了完整的父子线程值传递功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class InheritableThreadLocal <T> extends ThreadLocal <T> { protected T childValue (T parentValue) { return parentValue; } ThreadLocalMap getMap (Thread t) { return t.inheritableThreadLocals; } void createMap (Thread t, T firstValue) { t.inheritableThreadLocals = new ThreadLocalMap (this , firstValue); } }
设计精妙之处 :通过重写 getMap() 和 createMap() 两个方法,将所有 InheritableThreadLocal 的值存储在独立的 inheritableThreadLocals Map 中,与普通 ThreadLocal 完全隔离。这样在创建子线程时,只需复制 inheritableThreadLocals,而不影响 threadLocals。
Thread.init() 方法逐行解析
线程的构造器里隐藏着继承的核心逻辑。下面是 Thread.init() 方法的逐行中文注释 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 private void init (ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { if (name == null ) { throw new NullPointerException ("name cannot be null" ); } this .name = name; Thread parent = currentThread(); SecurityManager security = System.getSecurityManager(); if (g == null ) { if (security != null ) { g = security.getThreadGroup(); } if (g == null ) { g = parent.getThreadGroup(); } } g.checkAccess(); if (security != null ) { if (isCCLOverridden(getClass())) { security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION); } } g.addUnstarted(); this .group = g; this .daemon = parent.isDaemon(); this .priority = parent.getPriority(); if (security == null || isCCLOverridden(parent.getClass())) { this .contextClassLoader = parent.getContextClassLoader(); } else { this .contextClassLoader = parent.contextClassLoader; } this .inheritedAccessControlContext = acc != null ? acc : AccessController.getContext(); this .target = target; setPriority(priority); if (inheritThreadLocals && parent.inheritableThreadLocals != null ) { this .inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); } this .stackSize = stackSize; tid = nextThreadID(); }
继承流程图解
Thread 为 InheritableThreadLocal 的专门改造
问题 :Thread 类是否为 InheritableThreadLocal 专门改造过?
答案 :是的,Thread 类进行了以下专门改造:
新增字段 :inheritableThreadLocals 字段专门用于存储可继承的 ThreadLocal 值
init() 方法增强 :添加了 inheritThreadLocals 参数和复制逻辑
createInheritedMap() 方法 :ThreadLocal 类中专门提供了创建继承 Map 的静态方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 static ThreadLocalMap createInheritedMap (ThreadLocalMap parentMap) { return new ThreadLocalMap (parentMap); }private ThreadLocalMap (ThreadLocalMap parentMap) { Entry[] parentTable = parentMap.table; int len = parentTable.length; setThreshold(len); table = new Entry [len]; for (int j = 0 ; j < len; j++) { Entry e = parentTable[j]; if (e != null ) { @SuppressWarnings("unchecked") ThreadLocal<Object> key = (ThreadLocal<Object>) e.get(); if (key != null ) { Object value = key.childValue(e.value); Entry c = new Entry (key, value); int h = key.threadLocalHashCode & (len - 1 ); while (table[h] != null ) h = nextIndex(h, len); table[h] = c; size++; } } } }
为什么需要两个 Map?不能合并吗?
问题 :为什么 Thread 要有 threadLocals 和 inheritableThreadLocals 两个 Map,不能合并成一个吗?
答案 :不能合并,原因如下:
不能合并的四个核心原因 :
原因
说明
1. 语义隔离
普通 ThreadLocal 的设计初衷是线程隔离 ,不应该被子线程看到;InheritableThreadLocal 的设计初衷是父子传递 。两者语义完全相反
2. 安全性
如果合并,敏感的 ThreadLocal 值(如数据库连接、事务状态)会意外被子线程继承,造成安全隐患
3. 性能优化
分开存储后,创建子线程时只需复制 inheritableThreadLocals,而不是全部 ThreadLocal 值,减少开销
4. 选择性继承
开发者可以明确选择哪些变量需要继承(使用 InheritableThreadLocal),哪些不需要(使用普通 ThreadLocal)
继承流程对比 :
1 2 3 4 5 6 7 8 9 使用两个 Map(当前设计): 父线程 ├─ threadLocals (不继承) ────────────────> 子线程 threadLocals (空) └─ inheritableThreadLocals (继承) ──复制──> 子线程 inheritableThreadLocals 如果只有一个 Map(假设): 父线程 └─ threadLocals ──全部复制──> 子线程 threadLocals ↑ 问题:无法区分哪些应该继承,哪些不应该
InheritableThreadLocal 的浅拷贝问题
InheritableThreadLocal 在继承时存在浅拷贝问题 。childValue() 方法默认直接返回父线程的值引用,而不是深拷贝:
1 2 3 4 protected T childValue (T parentValue) { return parentValue; }
问题演示 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 InheritableThreadLocal<List<String>> context = new InheritableThreadLocal <>(); List<String> list = new ArrayList <>(); list.add("parent-item" ); context.set(list);new Thread (() -> { List<String> childList = context.get(); childList.add("child-item" ); System.out.println("子线程: " + childList); }).start(); Thread.sleep(100 ); System.out.println("父线程: " + context.get());
解决方案 :重写 childValue() 方法实现深拷贝:
1 2 3 4 5 6 7 InheritableThreadLocal<List<String>> context = new InheritableThreadLocal <>() { @Override protected List<String> childValue (List<String> parentValue) { return new ArrayList <>(parentValue); } };
InheritableThreadLocal 的局限性
InheritableThreadLocal 的局限性 :它只在创建子线程时 复制父线程的值。如果使用线程池,线程是复用的,不会每次都创建新线程,因此 InheritableThreadLocal 在线程池场景下无法正确传递上下文 。
这就是为什么需要 TransmittableThreadLocal ——InheritableThreadLocal 对线程池极不友好,无法满足现代应用中大量使用线程池的场景。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 InheritableThreadLocal<String> context = new InheritableThreadLocal <>();ExecutorService executor = Executors.newFixedThreadPool(1 ); context.set("request-1" ); executor.submit(() -> { System.out.println(context.get()); }); context.set("request-2" ); executor.submit(() -> { System.out.println(context.get()); });
原因 :线程池中的线程在第一次执行任务时就已经创建完成,此时继承了 request-1。后续任务复用这个线程时,不会再触发 Thread.init() 中的继承逻辑。
TransmittableThreadLocal:线程池场景的解决方案
阿里巴巴开源的 TransmittableThreadLocal (TTL) 解决了线程池场景下的上下文传递问题。
核心挑战:不能修改 Thread 类
InheritableThreadLocal 之所以能实现父子线程传递,是因为 JDK 对 Thread 类进行了专门改造 ——添加了 inheritableThreadLocals 字段和 init() 方法中的复制逻辑。
但对于第三方库(如 TTL),无法修改 JDK 的 Thread 类 。那么如何在不修改 Thread 的情况下,实现线程池场景的上下文传递呢?
TTL 的巧妙解决方案:Capture-Replay-Restore
TTL 采用了一种完全不同的思路——在任务层面而非线程层面 解决问题:
核心实现原理
1. holder 注册机制
TTL 的关键创新是引入了一个全局注册表 ,记录所有 TTL 实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public class TransmittableThreadLocal <T> extends InheritableThreadLocal <T> { private static InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<?>, ?>> holder = new InheritableThreadLocal <WeakHashMap<TransmittableThreadLocal<?>, ?>>() { @Override protected WeakHashMap<TransmittableThreadLocal<?>, ?> initialValue() { return new WeakHashMap <>(); } @Override protected WeakHashMap<TransmittableThreadLocal<?>, ?> childValue( WeakHashMap<TransmittableThreadLocal<?>, ?> parentValue) { return new WeakHashMap <>(parentValue); } }; @Override public final void set (T value) { super .set(value); if (value != null ) { holder.get().put(this , null ); } else { holder.get().remove(this ); } } }
设计精妙之处 :
使用 WeakHashMap 避免内存泄漏
holder 本身是 InheritableThreadLocal,确保子线程能继承注册表
每次 set() 时自动注册,capture() 时遍历注册表获取所有 TTL 值
2. 拦截线程池的 execute 方法
TTL 解决线程池传递问题的核心是拦截线程池的 execute() 方法 。无论是 TtlExecutors 包装还是 Java Agent,本质上都是在任务提交时进行拦截:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class ExecutorServiceTtlWrapper implements ExecutorService { private final ExecutorService executorService; @Override public void execute (Runnable command) { executorService.execute(TtlRunnable.get(command)); } @Override public <T> Future<T> submit (Callable<T> task) { return executorService.submit(TtlCallable.get(task)); } }
Java Agent 的字节码增强 :
1 2 3 4 5 6 7 public void execute (Runnable command) { command = TtlRunnable.get(command, false , true ); }
3. Worker 线程的 ThreadLocalMap Store/Restore
TTL 最关键的设计是处理工作线程原有 ThreadLocalMap 的保存和恢复 。这是防止数据污染的核心机制:
Store/Restore 的源码实现 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Override public void run () { Object captured = capturedRef.get(); Object backup = Transmitter.replay(captured); try { runnable.run(); } finally { Transmitter.restore(backup); } }
为什么需要 Store/Restore?
场景
不做 Restore 的问题
任务 A 设置了 TTL 值
任务 B 复用同一个工作线程时,会读到任务 A 的值
工作线程有自己的 TTL 值
任务执行后,工作线程原有的值被覆盖,影响后续逻辑
任务执行中修改了 TTL 值
修改会"泄漏"到后续任务,造成数据污染
Store/Restore 确保 :
任务执行时只能看到提交任务时 父线程传递的值
任务执行完毕后,工作线程恢复到执行任务前 的状态
任务之间完全隔离,互不影响
4. Transmitter 工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 public static class Transmitter { public static Object capture () { return new Snapshot (captureTtlValues(), captureThreadLocalValues()); } private static WeakHashMap<TransmittableThreadLocal<Object>, Object> captureTtlValues () { WeakHashMap<TransmittableThreadLocal<Object>, Object> ttl2Value = new WeakHashMap <>(); for (TransmittableThreadLocal<Object> threadLocal : holder.get().keySet()) { ttl2Value.put(threadLocal, threadLocal.copyValue()); } return ttl2Value; } public static Object replay (Object captured) { Snapshot capturedSnapshot = (Snapshot) captured; return new Snapshot ( replayTtlValues(capturedSnapshot.ttl2Value), replayThreadLocalValues(capturedSnapshot.threadLocal2Value) ); } private static WeakHashMap<TransmittableThreadLocal<Object>, Object> replayTtlValues ( WeakHashMap<TransmittableThreadLocal<Object>, Object> captured) { WeakHashMap<TransmittableThreadLocal<Object>, Object> backup = new WeakHashMap <>(); for (Iterator<TransmittableThreadLocal<Object>> iterator = holder.get().keySet().iterator(); iterator.hasNext(); ) { TransmittableThreadLocal<Object> threadLocal = iterator.next(); backup.put(threadLocal, threadLocal.get()); if (!captured.containsKey(threadLocal)) { iterator.remove(); threadLocal.superRemove(); } } for (Map.Entry<TransmittableThreadLocal<Object>, Object> entry : captured.entrySet()) { entry.getKey().set(entry.getValue()); } return backup; } public static void restore (Object backup) { Snapshot backupSnapshot = (Snapshot) backup; restoreTtlValues(backupSnapshot.ttl2Value); } }
3. TtlRunnable 包装器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public final class TtlRunnable implements Runnable { private final AtomicReference<Object> capturedRef; private final Runnable runnable; private final boolean releaseTtlValueReferenceAfterRun; private TtlRunnable (Runnable runnable, boolean releaseTtlValueReferenceAfterRun) { this .capturedRef = new AtomicReference <>(capture()); this .runnable = runnable; this .releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun; } @Override public void run () { Object captured = capturedRef.get(); if (captured == null || (releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null ))) { throw new IllegalStateException ("TTL value reference is released!" ); } Object backup = replay(captured); try { runnable.run(); } finally { restore(backup); } } public static TtlRunnable get (Runnable runnable) { if (runnable == null ) return null ; if (runnable instanceof TtlRunnable) return (TtlRunnable) runnable; return new TtlRunnable (runnable, false ); } }
为什么需要清理不在快照中的 TTL 变量?
这是 TTL 设计中最精妙的部分。考虑以下场景:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 TransmittableThreadLocal<String> context = new TransmittableThreadLocal <>();ExecutorService executor = Executors.newFixedThreadPool(1 ); context.set("task1-value" ); executor.submit(TtlRunnable.get(() -> { System.out.println(context.get()); context.set("modified-in-task1" ); })); executor.submit(TtlRunnable.get(() -> { System.out.println(context.get()); }));
清理逻辑确保 :任务执行时只能访问提交任务时 父线程传递的值,而不是工作线程之前执行其他任务时遗留的值。
三种使用方式对比
方式
侵入性
实现原理
适用场景
TtlRunnable 包装
高
手动包装每个任务
少量任务需要传递上下文
TtlExecutors 包装
中
装饰器模式包装线程池
特定线程池需要传递上下文
Java Agent
无
字节码增强,自动包装
全局透明传递,推荐生产使用
方式一:修饰 Runnable/Callable
1 2 3 4 5 6 7 8 9 TransmittableThreadLocal<String> context = new TransmittableThreadLocal <>(); context.set("value-set-in-parent" );Runnable ttlRunnable = TtlRunnable.get(() -> { System.out.println(context.get()); }); executorService.submit(ttlRunnable);
方式二:修饰线程池
1 2 3 4 5 6 7 8 ExecutorService executorService = Executors.newFixedThreadPool(10 ); executorService = TtlExecutors.getTtlExecutorService(executorService); executorService.submit(() -> { System.out.println(context.get()); });
方式三:Java Agent 方式(推荐)
通过 Java Agent 在类加载时自动增强线程池,无需修改业务代码:
1 java -javaagent:transmittable-thread-local-x.x.x.jar -jar your-app.jar
Java Agent 的实现原理是在类加载时修改 ThreadPoolExecutor、ScheduledThreadPoolExecutor、ForkJoinPool 等类的字节码,自动将提交的 Runnable/Callable 包装为 TTL 版本。
TTL 核心数据结构图
TTL 完整生命周期图
TTL 多任务隔离机制图
TTL 三种使用方式对比图
holder 注册机制详解图
InheritableThreadLocal vs TransmittableThreadLocal
适用场景
分布式追踪 :TraceId、SpanId 的跨线程传递
日志上下文 :MDC(Mapped Diagnostic Context)的传递
用户上下文 :用户身份信息、租户信息的传递
事务上下文 :分布式事务的上下文传递
ThreadLocal 核心源码深度解析
ThreadLocal.set() 源码详解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 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); }
ThreadLocal.get() 源码详解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 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; }protected T initialValue () { return null ; }
ThreadLocalMap.set() 源码详解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 private void set (ThreadLocal<?> key, Object value) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len - 1 ); for (Entry e = tab[i]; e != null ; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); if (k == key) { e.value = value; return ; } if (k == null ) { replaceStaleEntry(key, value, i); return ; } } tab[i] = new Entry (key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) { rehash(); } }private static int nextIndex (int i, int len) { return ((i + 1 < len) ? i + 1 : 0 ); }
ThreadLocalMap.getEntry() 源码详解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 private Entry getEntry (ThreadLocal<?> key) { int i = key.threadLocalHashCode & (table.length - 1 ); Entry e = table[i]; if (e != null && e.get() == key) { return e; } else { return getEntryAfterMiss(key, i, e); } }private Entry getEntryAfterMiss (ThreadLocal<?> key, int i, Entry e) { Entry[] tab = table; int len = tab.length; while (e != null ) { ThreadLocal<?> k = e.get(); if (k == key) { return e; } if (k == null ) { expungeStaleEntry(i); } else { i = nextIndex(i, len); } e = tab[i]; } return null ; }
expungeStaleEntry() 核心清理逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 private int expungeStaleEntry (int staleSlot) { Entry[] tab = table; int len = tab.length; tab[staleSlot].value = null ; tab[staleSlot] = null ; size--; Entry e; int i; for (i = nextIndex(staleSlot, len); (e = tab[i]) != null ; i = nextIndex(i, len)) { ThreadLocal<?> k = e.get(); if (k == null ) { e.value = null ; tab[i] = null ; size--; } else { int h = k.threadLocalHashCode & (len - 1 ); if (h != i) { tab[i] = null ; while (tab[h] != null ) { h = nextIndex(h, len); } tab[h] = e; } } } return i; }
基于 ThreadLocal 的设计模式框架实例
实例1:Spring 的 RequestContextHolder
Spring 框架使用 ThreadLocal 实现请求上下文的线程隔离:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 public abstract class RequestContextHolder { private static final ThreadLocal<RequestAttributes> requestAttributesHolder = new NamedThreadLocal <>("Request attributes" ); private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder = new NamedInheritableThreadLocal <>("Request context" ); public static void resetRequestAttributes () { requestAttributesHolder.remove(); inheritableRequestAttributesHolder.remove(); } public static void setRequestAttributes (RequestAttributes attributes, boolean inheritable) { if (attributes == null ) { resetRequestAttributes(); } else { if (inheritable) { inheritableRequestAttributesHolder.set(attributes); requestAttributesHolder.remove(); } else { requestAttributesHolder.set(attributes); inheritableRequestAttributesHolder.remove(); } } } public static RequestAttributes getRequestAttributes () { RequestAttributes attributes = requestAttributesHolder.get(); if (attributes == null ) { attributes = inheritableRequestAttributesHolder.get(); } return attributes; } public static RequestAttributes currentRequestAttributes () throws IllegalStateException { RequestAttributes attributes = getRequestAttributes(); if (attributes == null ) { throw new IllegalStateException ( "No thread-bound request found: ..." ); } return attributes; } }
使用示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Override public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) { RequestContextHolder.setRequestAttributes( new ServletRequestAttributes (request, response)); return true ; }public void businessMethod () { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); String userId = request.getHeader("X-User-Id" ); }@Override public void afterCompletion (HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { RequestContextHolder.resetRequestAttributes(); }
实例2:SLF4J 的 MDC(Mapped Diagnostic Context)
SLF4J 的 MDC 使用 ThreadLocal 实现日志上下文的线程隔离:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 public class LogbackMDCAdapter implements MDCAdapter { final ThreadLocal<Map<String, String>> copyOnInheritThreadLocal = new InheritableThreadLocal <Map<String, String>>() { @Override protected Map<String, String> childValue ( Map<String, String> parentValue) { if (parentValue == null ) { return null ; } return new HashMap <>(parentValue); } }; public void put (String key, String val) { if (key == null ) { throw new IllegalArgumentException ("key cannot be null" ); } Map<String, String> map = copyOnInheritThreadLocal.get(); if (map == null ) { map = new HashMap <>(); copyOnInheritThreadLocal.set(map); } map.put(key, val); } public String get (String key) { Map<String, String> map = copyOnInheritThreadLocal.get(); if (map != null && key != null ) { return map.get(key); } return null ; } public void remove (String key) { Map<String, String> map = copyOnInheritThreadLocal.get(); if (map != null ) { map.remove(key); } } public void clear () { Map<String, String> map = copyOnInheritThreadLocal.get(); if (map != null ) { map.clear(); copyOnInheritThreadLocal.remove(); } } }
使用示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class TraceIdFilter implements Filter { @Override public void doFilter (ServletRequest request, ServletResponse response, FilterChain chain) { try { String traceId = UUID.randomUUID().toString(); MDC.put("traceId" , traceId); MDC.put("userId" , getUserId(request)); chain.doFilter(request, response); } finally { MDC.clear(); } } }
实例3:MyBatis 的 SqlSession 管理
MyBatis-Spring 使用 ThreadLocal 管理 SqlSession 的生命周期:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 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 void bindResource (Object key, Object value) { Map<Object, Object> map = resources.get(); if (map == null ) { map = new HashMap <>(); resources.set(map); } Object oldValue = map.put(key, value); if (oldValue != null ) { throw new IllegalStateException ( "Already value [" + oldValue + "] for key [" + key + "] bound to thread" ); } } public static Object getResource (Object key) { Map<Object, Object> map = resources.get(); if (map == null ) { return null ; } return map.get(key); } public static Object unbindResource (Object key) { Map<Object, Object> map = resources.get(); if (map == null ) { return null ; } Object value = map.remove(key); if (map.isEmpty()) { resources.remove(); } return value; } }
实例4:自定义租户上下文(多租户架构)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 public class TenantContext { private static final ThreadLocal<TenantInfo> TENANT_HOLDER = new ThreadLocal <>(); private static final TransmittableThreadLocal<TenantInfo> TTL_TENANT_HOLDER = new TransmittableThreadLocal <>(); public static class TenantInfo { private final String tenantId; private final String tenantName; private final String dataSourceKey; private final Map<String, Object> attributes; public TenantInfo (String tenantId, String tenantName, String dataSourceKey) { this .tenantId = tenantId; this .tenantName = tenantName; this .dataSourceKey = dataSourceKey; this .attributes = new HashMap <>(); } public String getTenantId () { return tenantId; } public String getTenantName () { return tenantName; } public String getDataSourceKey () { return dataSourceKey; } public Object getAttribute (String key) { return attributes.get(key); } public void setAttribute (String key, Object value) { attributes.put(key, value); } } public static void setTenant (TenantInfo tenant) { TTL_TENANT_HOLDER.set(tenant); } public static TenantInfo getTenant () { return TTL_TENANT_HOLDER.get(); } public static String getTenantId () { TenantInfo tenant = getTenant(); return tenant != null ? tenant.getTenantId() : null ; } public static void clear () { TTL_TENANT_HOLDER.remove(); } public static <T> T executeWithTenant (TenantInfo tenant, Supplier<T> action) { TenantInfo previous = getTenant(); try { setTenant(tenant); return action.get(); } finally { if (previous != null ) { setTenant(previous); } else { clear(); } } } public static void runWithTenant (TenantInfo tenant, Runnable action) { executeWithTenant(tenant, () -> { action.run(); return null ; }); } }public class TenantInterceptor implements HandlerInterceptor { @Override public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) { String tenantId = request.getHeader("X-Tenant-Id" ); if (tenantId != null ) { TenantInfo tenant = loadTenantInfo(tenantId); TenantContext.setTenant(tenant); } return true ; } @Override public void afterCompletion (HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { TenantContext.clear(); } private TenantInfo loadTenantInfo (String tenantId) { return new TenantInfo (tenantId, "租户名称" , "ds_" + tenantId); } }public class TenantDataSourceRouter extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey () { return TenantContext.getTenant() != null ? TenantContext.getTenant().getDataSourceKey() : "default" ; } }
实例5:安全上下文(类似 Spring Security)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 public class SecurityContextHolder { public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL" ; public static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL" ; public static final String MODE_GLOBAL = "MODE_GLOBAL" ; private static String strategyName = MODE_THREADLOCAL; private static SecurityContextHolderStrategy strategy; static { initialize(); } private static void initialize () { switch (strategyName) { case MODE_THREADLOCAL: strategy = new ThreadLocalSecurityContextHolderStrategy (); break ; case MODE_INHERITABLETHREADLOCAL: strategy = new InheritableThreadLocalSecurityContextHolderStrategy (); break ; case MODE_GLOBAL: strategy = new GlobalSecurityContextHolderStrategy (); break ; default : throw new IllegalArgumentException ("Unknown strategy: " + strategyName); } } public static void setContext (SecurityContext context) { strategy.setContext(context); } public static SecurityContext getContext () { return strategy.getContext(); } public static void clearContext () { strategy.clearContext(); } public static SecurityContext createEmptyContext () { return strategy.createEmptyContext(); } }interface SecurityContextHolderStrategy { void clearContext () ; SecurityContext getContext () ; void setContext (SecurityContext context) ; SecurityContext createEmptyContext () ; }class ThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy { private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal <>(); @Override public void clearContext () { contextHolder.remove(); } @Override public SecurityContext getContext () { SecurityContext ctx = contextHolder.get(); if (ctx == null ) { ctx = createEmptyContext(); contextHolder.set(ctx); } return ctx; } @Override public void setContext (SecurityContext context) { if (context == null ) { throw new IllegalArgumentException ("Context cannot be null" ); } contextHolder.set(context); } @Override public SecurityContext createEmptyContext () { return new SecurityContextImpl (); } }class InheritableThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy { private static final ThreadLocal<SecurityContext> contextHolder = new InheritableThreadLocal <>(); @Override public void clearContext () { contextHolder.remove(); } @Override public SecurityContext getContext () { SecurityContext ctx = contextHolder.get(); if (ctx == null ) { ctx = createEmptyContext(); contextHolder.set(ctx); } return ctx; } @Override public void setContext (SecurityContext context) { contextHolder.set(context); } @Override public SecurityContext createEmptyContext () { return new SecurityContextImpl (); } }interface SecurityContext { Authentication getAuthentication () ; void setAuthentication (Authentication authentication) ; }class SecurityContextImpl implements SecurityContext { private Authentication authentication; @Override public Authentication getAuthentication () { return authentication; } @Override public void setAuthentication (Authentication authentication) { this .authentication = authentication; } }interface Authentication { String getPrincipal () ; Collection<String> getAuthorities () ; boolean isAuthenticated () ; }
设计模式总结
最佳实践
如何正确使用 ThreadLocal
ThreadLocal 最好的用法是做一个 request scope 的缓存 ——在请求开始时设置,请求结束时清理。在线程里长期复用 ThreadLocal 其实极度危险。
正确的使用模式 :
1 2 3 4 5 6 7 8 9 10 private static final ThreadLocal<SomeObject> threadLocal = new ThreadLocal <>();public void doSomething () { try { threadLocal.set(new SomeObject ()); } finally { threadLocal.remove(); } }
最佳实践清单 :
手动清理 :使用完 ThreadLocal 后立即调用 remove(),这是最可靠的方式
声明为 static :ThreadLocal 变量应该声明为 static,避免每个对象实例都创建新的 ThreadLocal
避免静态变量泄漏 :谨慎管理 ThreadLocal 静态变量的生命周期
线程池场景特别注意 :在使用线程池时,线程不会被销毁,必须手动清理
使用 try-finally :确保在 finally 块中调用 remove(),即使发生异常也能清理
WeakHashMap 与 ThreadLocalMap 的设计对比
WeakHashMap 和 ThreadLocalMap 有相似的设计理念——利用弱引用实现自动清理。
WeakHashMap 利用下一次操作来触发 clear,好像有一个后台线程来维护 Map 一样。这种"惰性清理"的设计模式在 ThreadLocalMap 中也有体现:只有在 get/set/remove 操作时才会触发 Stale Entry 的清理。
这种设计的优点是避免了额外的清理线程开销,缺点是如果长时间没有操作,过期数据不会被及时清理。
总结
ThreadLocal 是 Java 并发编程中实现线程封闭的核心工具,其设计体现了几个重要的工程智慧:
反转持有关系 :让 Thread 持有 Map,而不是让 Map 持有 Thread,从根本上避免了线程无法回收的问题
弱引用 + 惰性清理 :通过弱引用 key 和惰性清理机制,在不影响性能的前提下尽可能避免内存泄漏
开放地址法 :针对 ThreadLocal 的使用特点(Entry 数量少、需要顺便清理 Stale Entry),选择了更合适的哈希冲突解决方案
在实际使用中,要牢记:
ThreadLocal 应该声明为 static
使用完毕后必须调用 remove()
线程池场景下考虑使用 TransmittableThreadLocal