抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

ThreadLocal是Java中实现线程隔离的重要工具,它为每个线程提供独立的变量副本,避免了线程安全问题。本文深入剖析ThreadLocal的实现原理、内存泄漏问题及最佳实践。

什么是ThreadLocal

ThreadLocal提供线程局部变量,每个访问该变量的线程都有自己独立的副本,互不干扰。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
┌─────────────────────────────────────────────────────────────────┐
│ ThreadLocal工作方式 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 普通变量(共享): │
│ │
│ Thread-1 ────┐ │
│ ├────▶ 共享变量 ◀──── 需要同步! │
│ Thread-2 ────┘ │
│ │
│ │
│ ThreadLocal(隔离): │
│ │
│ Thread-1 ────────▶ 副本1 ┐ │
│ ├── 各自独立,无需同步 │
│ Thread-2 ────────▶ 副本2 ┘ │
│ │
└─────────────────────────────────────────────────────────────────┘

基本使用

创建和使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 创建ThreadLocal
ThreadLocal<String> threadLocal = new ThreadLocal<>();

// 设置值
threadLocal.set("Hello");

// 获取值
String value = threadLocal.get(); // "Hello"

// 移除值
threadLocal.remove();

// 获取已移除的值
String value2 = threadLocal.get(); // null

带初始值的ThreadLocal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 方式1:重写initialValue
ThreadLocal<SimpleDateFormat> dateFormat = new ThreadLocal<>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};

// 方式2:使用withInitial(推荐)
ThreadLocal<SimpleDateFormat> dateFormat2 =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));

// 首次get时自动初始化
SimpleDateFormat sdf = dateFormat.get(); // 不会是null

多线程验证隔离性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class ThreadLocalDemo {
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();

public static void main(String[] args) {
// 线程1
new Thread(() -> {
threadLocal.set(100);
System.out.println("Thread-1: " + threadLocal.get()); // 100
threadLocal.remove();
}).start();

// 线程2
new Thread(() -> {
threadLocal.set(200);
System.out.println("Thread-2: " + threadLocal.get()); // 200
threadLocal.remove();
}).start();

// 主线程
threadLocal.set(300);
System.out.println("Main: " + threadLocal.get()); // 300
threadLocal.remove();
}
}

底层原理

整体结构

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
┌─────────────────────────────────────────────────────────────────────┐
│ ThreadLocal存储结构 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Thread对象 │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ threadLocals: ThreadLocalMap │ │
│ │ ┌─────────────────────────────────────────────────────────┐│ │
│ │ │ Entry[] table ││ │
│ │ │ ┌─────────────┬─────────────┬─────────────┬──────────┐││ │
│ │ │ │ Entry[0] │ Entry[1] │ Entry[2] │ ... │││ │
│ │ │ │ ┌─────────┐ │ ┌─────────┐ │ ┌─────────┐ │ │││ │
│ │ │ │ │key(弱引用)│ │key(弱引用)│ │key(弱引用)│ │ │││ │
│ │ │ │ │ ↓ │ │ │ ↓ │ │ │ ↓ │ │ │││ │
│ │ │ │ │ThreadLoc│ │ │ThreadLoc│ │ │ThreadLoc│ │ │││ │
│ │ │ │ │al实例A │ │ │al实例B │ │ │al实例C │ │ │││ │
│ │ │ │ ├─────────┤ │ ├─────────┤ │ ├─────────┤ │ │││ │
│ │ │ │ │ value │ │ │ value │ │ │ value │ │ │││ │
│ │ │ │ │ "数据A" │ │ │ "数据B" │ │ │ "数据C" │ │ │││ │
│ │ │ │ └─────────┘ │ └─────────┘ │ └─────────┘ │ │││ │
│ │ │ └─────────────┴─────────────┴─────────────┴──────────┘││ │
│ │ └─────────────────────────────────────────────────────────┘│ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 注意:数据存储在Thread中,而不是ThreadLocal中 │
│ │
└─────────────────────────────────────────────────────────────────────┘

Thread类中的字段

1
2
3
4
5
6
7
public class Thread implements Runnable {
// 每个线程都有自己的ThreadLocalMap
ThreadLocal.ThreadLocalMap threadLocals = null;

// 用于InheritableThreadLocal
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}

ThreadLocalMap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static class ThreadLocalMap {
// Entry继承弱引用
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value; // 存储的值(强引用)

Entry(ThreadLocal<?> k, Object v) {
super(k); // key是弱引用
value = v;
}
}

// Entry数组,使用开放地址法解决哈希冲突
private Entry[] table;

// 初始容量
private static final int INITIAL_CAPACITY = 16;

// 扩容阈值
private int threshold;
}

set方法源码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取线程的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
// map已存在,直接设置值
map.set(this, value);
} else {
// map不存在,创建并设置
createMap(t, value);
}
}

ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}

void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}

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
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
// 以当前ThreadLocal实例为key获取Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// map为null或entry为null,返回初始值
return setInitialValue();
}

private T setInitialValue() {
T value = initialValue(); // 默认返回null,可重写
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
return 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
32
33
34
35
36
37
38
39
// ThreadLocalMap使用线性探测法解决哈希冲突
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) {
// 找到相同的key,更新value
e.value = value;
return;
}

if (k == null) {
// key被GC回收了(弱引用),替换这个位置
replaceStaleEntry(key, value, i);
return;
}
}

// 找到空位,插入新Entry
tab[i] = new Entry(key, value);
int sz = ++size;
// 清理过期Entry,如果没清理到且超过阈值,则扩容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}

// 下一个索引(环形数组)
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}

神奇的哈希码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ThreadLocal<T> {
// 每个ThreadLocal实例的哈希码
private final int threadLocalHashCode = nextHashCode();

// 原子递增
private static AtomicInteger nextHashCode = new AtomicInteger();

// 黄金分割数,可以让哈希分布更均匀
private static final int HASH_INCREMENT = 0x61c88647;

private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
}
1
2
3
4
5
6
7
8
9
10
0x61c88647 是黄金分割数的整数形式

生成的哈希值序列:
0x00000000
0x61c88647
0xc3910c8e
0x2559d2d5
...

这个序列在2的幂次大小的数组中分布非常均匀

内存泄漏问题

弱引用的设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌─────────────────────────────────────────────────────────────────────┐
│ Entry的引用关系 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 栈 堆 │
│ ┌──────┐ │
│ │ ref │─────强引用────▶ ThreadLocal实例 │
│ └──────┘ ▲ │
│ │ │
│ 弱引用 │
│ │ │
│ Thread ───▶ ThreadLocalMap ───▶ Entry │
│ │ │
│ 强引用 │
│ │ │
│ ▼ │
│ Value │
│ │
└─────────────────────────────────────────────────────────────────────┘

为什么Entry的key是弱引用?

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
假设Entry的key是强引用:

ref ─────────────▶ ThreadLocal实例 ◀───────┐
│ ▲ │
│ │ 强引用
ref = null │ │
│ ThreadLocalMap ───▶ Entry─┘
▼ ▲
引用断开 Thread

问题:即使ref=null,ThreadLocal实例仍被Entry强引用
只要线程存活,ThreadLocal就无法被GC
特别是在线程池场景下,线程会被复用,导致内存泄漏


使用弱引用后:

ref ─────────────▶ ThreadLocal实例 ◀───────┐
│ ▲ │
│ │ 弱引用
ref = null │ │
│ ThreadLocalMap ───▶ Entry─┘
▼ ▲
引用断开 Thread

GC时:ThreadLocal实例只有弱引用,可以被回收
Entry的key变为null

但Value仍然泄漏!

1
2
3
4
5
6
7
8
9
GC回收ThreadLocal后:

ThreadLocalMap ───▶ Entry

key = null(已被GC)

value ───▶ 实际数据(强引用!)

问题:Entry还在,value还被强引用,无法GC

内存泄漏示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MemoryLeakDemo {
// 模拟线程池场景
private static ExecutorService executor = Executors.newFixedThreadPool(1);

public static void main(String[] args) throws Exception {
for (int i = 0; i < 1000; i++) {
executor.execute(() -> {
ThreadLocal<byte[]> local = new ThreadLocal<>();
local.set(new byte[1024 * 1024]); // 1MB数据
// 没有调用remove()!
// local变量离开作用域后,ThreadLocal实例被GC
// 但value(1MB数据)仍然存在于Entry中
});
}
// 1个线程累积了1000MB数据!
}
}

ThreadLocalMap的清理机制

ThreadLocalMap在操作时会主动清理key为null的Entry:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// get时清理
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); // 会清理过期Entry
}

// set时清理
private void set(ThreadLocal<?> key, Object value) {
// ... 在探测过程中会清理过期Entry
replaceStaleEntry(key, value, i);
// ...
}

// 扩容时清理
private void rehash() {
expungeStaleEntries(); // 清理所有过期Entry
// ...
}

但这种清理是被动的、不彻底的,最佳实践仍然是手动调用remove()

正确使用方式

使用try-finally确保清理

1
2
3
4
5
6
7
8
9
10
11
private static final ThreadLocal<User> userHolder = new ThreadLocal<>();

public void doSomething() {
try {
userHolder.set(getCurrentUser());
// 业务逻辑
processRequest();
} finally {
userHolder.remove(); // 必须清理!
}
}

封装成工具类

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
public class UserContext {
private static final ThreadLocal<User> USER_HOLDER = new ThreadLocal<>();

public static void setUser(User user) {
USER_HOLDER.set(user);
}

public static User getUser() {
return USER_HOLDER.get();
}

public static void clear() {
USER_HOLDER.remove();
}

// 提供自动清理的方式
public static void runWithUser(User user, Runnable task) {
try {
setUser(user);
task.run();
} finally {
clear();
}
}

public static <T> T callWithUser(User user, Callable<T> task) throws Exception {
try {
setUser(user);
return task.call();
} finally {
clear();
}
}
}

InheritableThreadLocal

普通ThreadLocal在子线程中无法获取父线程的值:

1
2
3
4
5
6
ThreadLocal<String> local = new ThreadLocal<>();
local.set("父线程的值");

new Thread(() -> {
System.out.println(local.get()); // null!
}).start();

InheritableThreadLocal可以实现父子线程传递:

1
2
3
4
5
6
InheritableThreadLocal<String> local = new InheritableThreadLocal<>();
local.set("父线程的值");

new Thread(() -> {
System.out.println(local.get()); // "父线程的值"
}).start();

原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Thread {
ThreadLocal.ThreadLocalMap threadLocals;
ThreadLocal.ThreadLocalMap inheritableThreadLocals; // 用于继承

// 创建子线程时
private Thread(ThreadGroup g, Runnable target, String name, long stackSize,
AccessControlContext acc, boolean inheritThreadLocals) {
// ...
Thread parent = currentThread();
if (inheritThreadLocals && parent.inheritableThreadLocals != null) {
// 复制父线程的inheritableThreadLocals到子线程
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
}
}
}

线程池场景的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
InheritableThreadLocal<String> local = new InheritableThreadLocal<>();
ExecutorService executor = Executors.newFixedThreadPool(1);

local.set("任务1的值");
executor.execute(() -> {
System.out.println(local.get()); // "任务1的值" ✓
});

Thread.sleep(100);

local.set("任务2的值");
executor.execute(() -> {
System.out.println(local.get()); // 还是"任务1的值"!✗
});

// 问题:线程池的线程是复用的,不会再次执行"继承"逻辑

TransmittableThreadLocal

阿里开源的TransmittableThreadLocal(TTL)解决了线程池场景的传递问题。

引入依赖

1
2
3
4
5
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.14.2</version>
</dependency>

基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.threadpool.TtlExecutors;

TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();

// 包装线程池
ExecutorService executor = TtlExecutors.getTtlExecutorService(
Executors.newFixedThreadPool(1)
);

context.set("任务1的值");
executor.execute(() -> {
System.out.println(context.get()); // "任务1的值" ✓
});

Thread.sleep(100);

context.set("任务2的值");
executor.execute(() -> {
System.out.println(context.get()); // "任务2的值" ✓ 正确传递!
});

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
30
┌─────────────────────────────────────────────────────────────────────┐
│ TransmittableThreadLocal原理 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 提交任务时: │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 1. 捕获(capture)当前线程的所有TTL值 │ │
│ │ 2. 将TTL值和原始Runnable包装成TtlRunnable │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 执行任务时: │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 1. 备份(backup)执行线程当前的TTL值 │ │
│ │ 2. 重放(replay)捕获的TTL值到执行线程 │ │
│ │ 3. 执行原始Runnable │ │
│ │ 4. 恢复(restore)备份的TTL值 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ │
│ 父线程 子线程(线程池复用) │
│ ┌────────────┐ ┌────────────┐ │
│ │ TTL = "A" │──capture()──▶│ 保存"A" │ │
│ └────────────┘ │ │ │
│ │ backup() │ 备份原有值 │
│ │ replay() │ 设置"A" │
│ │ run() │ 执行任务 │
│ │ restore() │ 恢复原有值 │
│ └────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘

实际应用场景

1. 用户上下文传递

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
public class UserContextHolder {
private static final ThreadLocal<UserContext> HOLDER = new ThreadLocal<>();

public static void set(UserContext context) {
HOLDER.set(context);
}

public static UserContext get() {
return HOLDER.get();
}

public static Long getUserId() {
UserContext ctx = get();
return ctx != null ? ctx.getUserId() : null;
}

public static void clear() {
HOLDER.remove();
}
}

// 在Filter/Interceptor中设置
@Component
public class UserContextFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
try {
UserContext context = parseUserFromToken(request);
UserContextHolder.set(context);
chain.doFilter(request, response);
} finally {
UserContextHolder.clear(); // 必须清理
}
}
}

// 在业务代码中使用
@Service
public class OrderService {
public Order createOrder(OrderRequest request) {
Long userId = UserContextHolder.getUserId(); // 获取当前用户
// ...
}
}

2. 数据库连接/事务

1
2
3
4
5
6
7
8
// Spring事务管理就是使用ThreadLocal存储数据库连接
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");
}

3. 日期格式化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// SimpleDateFormat不是线程安全的
// 使用ThreadLocal为每个线程创建独立实例

public class DateUtils {
private static final ThreadLocal<SimpleDateFormat> DATE_FORMAT =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));

private static final ThreadLocal<SimpleDateFormat> DATETIME_FORMAT =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

public static String formatDate(Date date) {
return DATE_FORMAT.get().format(date);
}

public static String formatDateTime(Date date) {
return DATETIME_FORMAT.get().format(date);
}

public static Date parseDate(String str) throws ParseException {
return DATE_FORMAT.get().parse(str);
}
}

4. 链路追踪

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class TraceContext {
private static final ThreadLocal<String> TRACE_ID = new ThreadLocal<>();
private static final ThreadLocal<String> SPAN_ID = new ThreadLocal<>();

public static void setTraceId(String traceId) {
TRACE_ID.set(traceId);
}

public static String getTraceId() {
return TRACE_ID.get();
}

public static void clear() {
TRACE_ID.remove();
SPAN_ID.remove();
}
}

// 日志输出时自动带上TraceId
// logback-spring.xml
// <pattern>%d{yyyy-MM-dd HH:mm:ss} [%X{traceId}] %-5level %logger - %msg%n</pattern>

5. 分页参数传递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// PageHelper的实现方式
public class PageHelper {
private static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<>();

public static <E> Page<E> startPage(int pageNum, int pageSize) {
Page<E> page = new Page<>(pageNum, pageSize);
LOCAL_PAGE.set(page);
return page;
}

public static <E> Page<E> getLocalPage() {
return LOCAL_PAGE.get();
}

public static void clearPage() {
LOCAL_PAGE.remove();
}

// 在MyBatis拦截器中使用
// 执行SQL前获取分页参数,执行后清理
}

与synchronized的对比

特性 synchronized 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
// 场景:多线程使用SimpleDateFormat

// 方式1:synchronized(共享一个实例)
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

public static String format1(Date date) {
synchronized (sdf) {
return sdf.format(date); // 串行执行
}
}

// 方式2:ThreadLocal(每个线程一个实例)
private static final ThreadLocal<SimpleDateFormat> sdfHolder =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));

public static String format2(Date date) {
return sdfHolder.get().format(date); // 并行执行,但占用更多内存
}

// 方式3:每次new(简单但开销大)
public static String format3(Date date) {
return new SimpleDateFormat("yyyy-MM-dd").format(date);
}

// 方式4:使用线程安全的DateTimeFormatter(推荐)
private static final DateTimeFormatter formatter =
DateTimeFormatter.ofPattern("yyyy-MM-dd");

public static String format4(LocalDate date) {
return formatter.format(date); // 线程安全,无需ThreadLocal
}

最佳实践总结

DO(应该做)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 1. 声明为static final
private static final ThreadLocal<User> userLocal = new ThreadLocal<>();

// 2. 使用withInitial提供初始值
private static final ThreadLocal<List<String>> listLocal =
ThreadLocal.withInitial(ArrayList::new);

// 3. 使用try-finally确保清理
try {
userLocal.set(user);
doSomething();
} finally {
userLocal.remove();
}

// 4. 线程池场景使用TTL
ExecutorService executor = TtlExecutors.getTtlExecutorService(
Executors.newFixedThreadPool(10)
);

DON’T(不应该做)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 1. 不要忘记remove
userLocal.set(user);
doSomething();
// 忘记remove(),导致内存泄漏

// 2. 不要在ThreadLocal中存储大对象
ThreadLocal<byte[]> dataLocal = new ThreadLocal<>();
dataLocal.set(new byte[10 * 1024 * 1024]); // 10MB!

// 3. 不要过度使用ThreadLocal
// 能通过参数传递的就不要用ThreadLocal
public void process(User user) { // 参数传递更清晰
// ...
}

总结

要点 说明
存储位置 数据存在Thread对象的ThreadLocalMap中
key ThreadLocal实例本身(弱引用)
value 用户设置的值(强引用)
内存泄漏 必须手动调用remove()清理
线程池传递 使用TransmittableThreadLocal
父子线程传递 使用InheritableThreadLocal

ThreadLocal是实现线程隔离的利器,但需要注意内存泄漏问题。正确使用方式是:用完即清理,声明为static final,线程池场景使用TTL