ThreadLocal的作用
ThreadLocal 是 Java 中提供的一种用于实现线程局部变量的机制。它的主要作用是:ThreadLocal 的核心作用是为每个线程提供独立的变量副本,实现线程间的数据隔离,保证线程安全,简化多线程编程。
具体作用包括:
- 线程隔离
ThreadLocal 为每个线程维护一份独立的变量副本,线程之间互不干扰,避免了多线程访问同一变量时的冲突和数据不一致。 - 简化线程安全编程
通过使用 ThreadLocal,可以避免使用同步锁(synchronized)来保护共享变量,从而简化代码并提高性能。 - 适用于线程相关的上下文信息存储
例如,用户会话信息、数据库连接、事务信息等,可以存储在线程的局部变量中,方便在同一线程的不同方法间共享数据。 - 避免参数传递
在复杂调用链中,可以通过 ThreadLocal 传递数据,避免在方法间显式传递参数。
ThreadLocal的使用场景举例
ThreadLocal 适用于需要在同一线程内共享数据但又避免线程间共享冲突的场景,常见于用户信息存储、数据库连接管理、事务控制、格式化工具缓存和日志追踪等。
以下是一些常见的 ThreadLocal 使用示例,涵盖不同场景
使用场景 | 说明 | 示例简述 |
---|---|---|
1. 线程安全的变量存储 | 为每个线程保存独立变量,避免多线程共享冲突 | 保存用户ID、请求ID等线程相关信息 |
2. 数据库连接管理 | 每个线程维护独立的数据库连接,避免多线程共享连接导致冲突 | 线程独立持有JDBC Connection,保证线程安全 |
3. 事务管理 | 线程内共享事务状态,方便事务的开启、提交和回滚 | 线程内保存事务对象,跨方法调用共享事务 |
4. 格式化工具缓存 | 线程安全的日期格式化工具,避免多线程环境下SimpleDateFormat的线程安全问题 | 每个线程持有独立的SimpleDateFormat实例 |
5. 用户会话信息存储 | 在线程中保存当前用户信息,方便业务逻辑中随时获取 | Web请求线程中保存当前登录用户信息 |
6. 日志追踪ID | 线程内保存唯一请求ID,方便日志追踪和关联 | 生成并保存请求唯一ID,日志打印时自动带 |
- 线程安全的变量存储
java
private static ThreadLocal<String> userIdThreadLocal = new ThreadLocal<>();
public void setUserId(String userId) {
userIdThreadLocal.set(userId);
}
public String getUserId() {
return userIdThreadLocal.get();
}
- 数据库连接管理
java
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<>();
public Connection getConnection() {
Connection conn = connectionHolder.get();
if (conn == null) {
conn = createNewConnection();
connectionHolder.set(conn);
}
return conn;
}
- 事务管理
java
private static ThreadLocal<Transaction> transactionHolder = new ThreadLocal<>();
public void beginTransaction() {
Transaction tx = new Transaction();
transactionHolder.set(tx);
tx.begin();
}
public void commitTransaction() {
Transaction tx = transactionHolder.get();
if (tx != null) {
tx.commit();
transactionHolder.remove();
}
}
- 格式化工具缓存
java
private static ThreadLocal<SimpleDateFormat> dateFormatHolder = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
public String formatDate(Date date) {
return dateFormatHolder.get().format(date);
}
- 用户会话信息存储
java
private static ThreadLocal<User> currentUser = new ThreadLocal<>();
public void setCurrentUser(User user) {
currentUser.set(user);
}
public User getCurrentUser() {
return currentUser.get();
}
- 日志追踪ID
java
private static ThreadLocal<String> traceId = new ThreadLocal<>();
public void setTraceId(String id) {
traceId.set(id);
}
public String getTraceId() {
return traceId.get();
}
ThreadLocal源码走读
ThreadLocal的类关系图如下,他主要是通过内部的ThreadLocalMap实现数据的线程隔离

ThreadLocal源码中提供了共有的接口set、get、remove等,私有的字段threadLocalHashCode、nextHashCode等主要用于搜索定位数据
java
public class ThreadLocal<T> {
// ThreadLocal对象作为key, 通过threadLocalHashCode进行搜索。
private final int threadLocalHashCode = nextHashCode();
// 下一个给出的hashcode。自动更新,从0开始
private static AtomicInteger nextHashCode = new AtomicInteger();
//连续生成的哈希码之间的差异:将隐式顺序线程本地 ID 转换为接近最优分布的乘法哈希值,适用于 2 的幂大小的表。
private static final int HASH_INCREMENT = 0x61c88647;
// 返回下一个hash code
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
// 初始化,实际是新建了一个SuppliedThreadLocal
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}
// 获取值
public T get() {
Thread t = Thread.currentThread();
// 获取通过thread获取ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
// 获取entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
// 返回值
return result;
}
}
// 为空的时候初始化一个ThreadLocalMap
return setInitialValue();
}
// 设置值
public void set(T value) {
Thread t = Thread.currentThread();
// 获取通过thread获取ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
// 更新值
map.set(this, value);
else // 为空时创建ThreadLocalMap
createMap(t, value);
}
// 删除值
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
// 获取ThreadLocalMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
}
ThreadLocalMap是ThreadLocal实现功能的主要内部类,他使用Entry数组维护线程数据,Entry是一个弱引用的对象,他是一个Map结构,以ThreadLocal为key,Object为value。

ThreadLocalMap的源码
java
static class ThreadLocalMap {
// <key,value> = <ThreadLocal<?>, Object> , 如果key=null,表明key不再被引用,就可以从table表中删除了
static class Entry extends WeakReference<ThreadLocal<?>> {
/** 关联到ThreadLocal的值 */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
/**
* 初始容量大小。必须是2的幂次方
*/
private static final int INITIAL_CAPACITY = 16;
/**
* Entry表table
*/
private Entry[] table;
/**
* table中元素的数量
*/
private int size = 0;
/**
* 下一个扩容的值
*/
private int threshold;
/**
* 索引i增加
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
/**
* 索引i减少
*/
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
// 根据key获取Entry
private Entry getEntry(ThreadLocal<?> key) {
// 根据key获得索引i
int i = key.threadLocalHashCode & (table.length - 1);
// 根据i获取Entry
Entry e = table[i];
if (e != null && e.get() == key) // 不为空时直接返回
return e;
else //
return getEntryAfterMiss(key, i, e);
}
/**
* 循环搜索Entry
*/
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) // k为空时,删除过期的Entry
expungeStaleEntry(i);
else // k不为空时,迭代下一个索引i
i = nextIndex(i, len);
e = tab[i]; // 更新e
}
return null;
}
/**
* 设置值
*/
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) { // 存在就更新value
e.value = value;
return;
}
if (k == null) { // 不存在,替换老的Entry
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
/**
* 删除Entry
*/
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;
}
}
}
// 其他省略。。。。
}
ThreadLocal为什么使用不当会发生OOM?
发生OOM的原因
- ThreadLocalMap 的设计
每个线程内部维护一个ThreadLocalMap
,用来存储该线程所有的 ThreadLocal 变量及其对应的值。这个ThreadLocalMap
的 key 是弱引用(WeakReference)指向 ThreadLocal 对象,value 是强引用指向实际的值。 - 弱引用的key被回收,但value未被清理
当 ThreadLocal 对象不再被外部引用时,弱引用的 key 会被垃圾回收,但对应的 value(实际存储的数据)仍然是强引用,不会自动被回收 。这就导致ThreadLocalMap
中残留了"key为null,value不为null"的条目。 - 残留的value导致内存泄漏
这些残留的 value 无法被清理,且随着线程长时间存活(如线程池中的线程),这些无用的对象会一直占用内存,最终可能导致内存溢出。 - 线程池环境下更易发生
在线程池中,线程是复用的,ThreadLocalMap 也会一直存在,如果不手动清理 ThreadLocal,残留的无用数据会不断累积,增加OOM风险。
如何避免 ThreadLocal 导致的OOM?
- 使用完毕后调用
ThreadLocal.remove()
主动清理当前线程中对应的 ThreadLocal 变量,避免残留。 - 避免长时间持有大对象
ThreadLocal 中存储的数据尽量避免占用大量内存,或者及时清理。 - 谨慎使用 ThreadLocal,尤其在线程池环境中
线程池线程复用时,ThreadLocal 的清理尤为重要。