ThreadLocal 是 Java 中提供的一个线程本地变量存储类。它让每个线程都能拥有自己独立的变量副本,实现了线程间的数据隔离。本文讲述ThreadLocal 的原理,使用场景及内存泄漏问题。
ThreadLocal核心特点:线程隔离:每个线程访问的是自己的变量副本;线程安全:无需同步,因为变量不共享;生命周期:与线程相同,线程结束时自动清理
|
1 2 3 4 5 6 7 8 9 10 11 |
// 每个 Thread 对象内部都有一个 ThreadLocalMap ThreadLocal.ThreadLocalMap threadLocals = null;
// ThreadLocalMap 内部使用 Entry 数组,Entry 继承自 WeakReference<ThreadLocal<?>> static class Entry extends WeakReference<ThreadLocal<?>> { Object value; Entry(ThreadLocal<?> k, Object v) { super(k); // 弱引用指向 ThreadLocal 实例 value = v; // 强引用指向实际存储的值 } } |
|
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 void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { map.set(this, value); // this指当前ThreadLocal实例 } else { createMap(t, value); } }
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();
// 找到相同的key,直接替换value if (k == key) { e.value = value; return; }
// key已被回收,替换过期条目 if (k == null) { replaceStaleEntry(key, value, i); return; } }
tab[i] = new Entry(key, value); int sz = ++size; // 清理并判断是否需要扩容 if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); } |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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(); // 返回初始值 } |
|
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 |
// 场景1:线程上下文信息传递(如Spring的RequestContextHolder) public class RequestContextHolder { private static final ThreadLocal<HttpServletRequest> requestHolder = new ThreadLocal<>();
public static void setRequest(HttpServletRequest request) { requestHolder.set(request); }
public static HttpServletRequest getRequest() { return requestHolder.get(); } }
// 场景2:数据库连接管理 public class ConnectionManager { private static ThreadLocal<Connection> connectionHolder = ThreadLocal.withInitial(() -> DriverManager.getConnection(url));
public static Connection getConnection() { return connectionHolder.get(); } }
// 场景3:用户会话信息 public class UserContext { private static ThreadLocal<UserInfo> userHolder = new ThreadLocal<>();
public static void setUser(UserInfo user) { userHolder.set(user); }
public static UserInfo getUser() { return userHolder.get(); } }
// 场景4:避免参数传递 public class TransactionContext { private static ThreadLocal<Transaction> transactionHolder = new ThreadLocal<>();
public static void beginTransaction() { transactionHolder.set(new Transaction()); }
public static Transaction getTransaction() { return transactionHolder.get(); } } |
|
1 2 3 4 5 6 7 8 9 10 11 |
强引用链: Thread → ThreadLocalMap → Entry[] → Entry → value (强引用)
弱引用: Entry → key (弱引用指向ThreadLocal)
泄漏场景: 1. ThreadLocal实例被回收 → key=null 2. 但value仍然被Entry强引用 3. 线程池中线程长期存活 → value无法被回收 4. 导致内存泄漏 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// 方案1:手动remove(推荐) try { threadLocal.set(value); // ... 业务逻辑 } finally { threadLocal.remove(); // 必须执行! }
// 方案2:使用InheritableThreadLocal(父子线程传递) ThreadLocal<String> parent = new InheritableThreadLocal<>(); parent.set("parent value");
new Thread(() -> { // 子线程可以获取父线程的值 System.out.println(parent.get()); // "parent value" }).start();
// 方案3:使用FastThreadLocal(Netty优化版) // 适用于高并发场景,避免了哈希冲突 |
|
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 |
public class SafeThreadLocalExample { // 1. 使用static final修饰 private static final ThreadLocal<SimpleDateFormat> DATE_FORMAT = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
// 2. 包装为工具类 public static Date parse(String dateStr) throws ParseException { SimpleDateFormat sdf = DATE_FORMAT.get(); try { return sdf.parse(dateStr); } finally { // 注意:这里通常不需要remove,因为要重用SimpleDateFormat // 但如果是用完即弃的场景,应该remove } }
// 3. 线程池场景必须清理 public void executeInThreadPool() { ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) { executor.submit(() -> { try { UserContext.setUser(new UserInfo()); // ... 业务处理 } finally { UserContext.remove(); // 关键! } }); } } } |
|
方案 |
适用场景 |
优点 |
缺点 |
|
ThreadLocal |
线程隔离数据 |
简单高效 |
内存泄漏风险 |
|
InheritableThreadLocal |
父子线程传递 |
继承上下文 |
线程池中失效 |
|
TransmittableThreadLocal |
线程池传递 |
线程池友好 |
引入依赖 |
|
参数传递 |
简单场景 |
无副作用 |
代码冗余 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// 查看ThreadLocalMap内容(调试用) public static void dumpThreadLocalMap(Thread thread) throws Exception { Field field = Thread.class.getDeclaredField("threadLocals"); field.setAccessible(true); Object map = field.get(thread);
if (map != null) { Field tableField = map.getClass().getDeclaredField("table"); tableField.setAccessible(true); Object[] table = (Object[]) tableField.get(map);
for (Object entry : table) { if (entry != null) { Field valueField = entry.getClass().getDeclaredField("value"); valueField.setAccessible(true); System.out.println("Key: " + ((WeakReference<?>) entry).get() + ", Value: " + valueField.get(entry)); } } } } |
ThreadLocal 是强大的线程隔离工具,但需要谨慎使用。在 Web 应用和线程池场景中,必须在 finally 块中调用 remove(),这是避免内存泄漏的关键。
关于 ThreadLocal,我从原理、场景和内存泄漏三个方面来说一下我的理解。
简单来说,ThreadLocal 是一个线程级别的变量隔离工具。它的设计目标就是让同一个变量,在不同的线程里有自己独立的副本,互不干扰。
正是因为这种线程隔离的特性,它特别适合用来传递一些需要在线程整个生命周期内、多个方法间共享,但又不能(或不想)通过方法参数显式传递的数据。最常见的有两个场景:
ThreadLocal 如果使用不当,确实可能导致内存泄漏。它的根源在于 ThreadLocalMap 中 Entry 的设计。
内存泄漏的关键是 “弱Key + 强Value + 长生命周期线程” 的组合。所以,把 remove() 放在 finally 块里调用,是一个必须养成的编程习惯。