ThreadLocal 底层原理
ThreadLocal
是 Java 中用于实现线程本地存储的类。每个线程都有自己独立的 ThreadLocal
变量副本,线程之间互不干扰。
底层实现
-
ThreadLocalMap:
-
每个
Thread
对象内部都有一个ThreadLocalMap
,用于存储线程本地的变量。 -
ThreadLocalMap
是一个定制化的HashMap
,键为ThreadLocal
实例,值为线程本地变量。
-
-
Entry:
-
ThreadLocalMap
使用Entry
类来存储键值对,Entry
继承自WeakReference<ThreadLocal<?>>
,键是弱引用,值则是强引用。
-
-
操作流程:
-
当调用
ThreadLocal.set(T value)
时,当前线程的ThreadLocalMap
会以ThreadLocal
实例为键,存储对应的值。 -
调用
ThreadLocal.get()
时,会从当前线程的ThreadLocalMap
中获取对应的值。
-
内存泄露问题
原因
-
弱引用键:
-
ThreadLocalMap
的键是弱引用,当ThreadLocal
实例不再被强引用时,键会被垃圾回收,但值仍然是强引用,不会被回收。
-
-
线程生命周期长:
-
如果线程长时间运行(如线程池中的线程),且
ThreadLocal
未被清理,会导致ThreadLocalMap
中积累大量无用的Entry
,造成内存泄露。
-
解决方法
-
显式调用 remove:
使用完ThreadLocal
后,调用remove()
方法清理当前线程的ThreadLocalMap
中的对应Entry
。 -
使用 try-finally:
在try
块中使用ThreadLocal
,在finally
块中调用remove()
确保清理。
ThreadLocal<String> threadLocal = new ThreadLocal<>();
try {threadLocal.set("value");// 使用 threadLocal
} finally {threadLocal.remove();
}
在项目中使用 ThreadLocal
典型场景
-
用户会话管理:
在 Web 应用中,将用户会话信息存储在ThreadLocal
中,方便在同一个线程中共享数据。 -
数据库连接管理:
在事务管理中,将数据库连接存储在ThreadLocal
中,确保同一个事务使用同一个连接。 -
日志跟踪:
在分布式系统中,将请求的唯一标识存储在ThreadLocal
中,方便日志跟踪。
示例代码
public class UserContext {private static final ThreadLocal<User> currentUser = new ThreadLocal<>();public static void setCurrentUser(User user) {currentUser.set(user);}public static User getCurrentUser() {return currentUser.get();}public static void clear() {currentUser.remove();}
}// 使用示例
User user = new User("123", "John Doe");
UserContext.setCurrentUser(user);
try {// 业务逻辑User currentUser = UserContext.getCurrentUser();System.out.println(currentUser.getName());
} finally {UserContext.clear();
}
总结
-
ThreadLocal
通过ThreadLocalMap
实现线程本地存储,每个线程有独立的变量副本。 -
使用不当会导致内存泄露,需及时调用
remove()
清理。 -
在项目中常用于会话管理、数据库连接管理和日志跟踪等场景。