📊 ThreadLocal 关系图(超简化版)
一、ThreadLocal核心机制
Thread → ThreadLocalMap → Entry(ThreadLocal, Value)
二、线程隔离原理
线程1 → ThreadLocalMap1 → Entry[ThreadLocal1 → "数据1"]
线程2 → ThreadLocalMap2 → Entry[ThreadLocal1 → "数据2"]
三、ThreadLocal在Looper中的应用
Looper.prepare() → ThreadLocal.set(new Looper()) → 绑定到当前线程
Looper.myLooper() → ThreadLocal.get() → 获取当前线程的Looper
2.1 ThreadLocal 基础概念
1. 什么是ThreadLocal?ThreadLocal的作用是什么?
完整答案:
ThreadLocal的定义: ThreadLocal是Java提供的线程局部变量,每个线程都有自己独立的变量副本,线程间互不干扰。
ThreadLocal的作用:
-
线程隔离:
- 每个线程有自己独立的变量副本
- 线程间数据互不干扰
-
避免线程安全问题:
- 不需要使用synchronized
- 天然线程安全
-
简化参数传递:
- 不需要通过方法参数传递
- 可以在任何地方访问
简单理解: ThreadLocal就像一个"线程专属的储物柜",每个线程都有自己的柜子,互不干扰。
代码示例:
// 创建ThreadLocal
ThreadLocal<String> threadLocal = new ThreadLocal<>();
// 线程1
new Thread(() -> {
threadLocal.set("线程1的数据");
System.out.println(threadLocal.get()); // 输出:线程1的数据
}).start();
// 线程2
new Thread(() -> {
threadLocal.set("线程2的数据");
System.out.println(threadLocal.get()); // 输出:线程2的数据
}).start();
// 每个线程有自己独立的数据,互不干扰
ThreadLocal的核心方法:
// 设置值
threadLocal.set("value");
// 获取值
String value = threadLocal.get();
// 移除值
threadLocal.remove();
ThreadLocal在Android中的应用:
// 1. Looper中使用ThreadLocal
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
// 2. 存储线程上下文信息
ThreadLocal<Context> contextThreadLocal = new ThreadLocal<>();
总结: ThreadLocal提供线程局部变量,每个线程有独立的变量副本,实现线程隔离和简化参数传递。
2. ThreadLocal和普通变量的区别是什么?
完整答案:
主要区别:
| 特性 | 普通变量 | ThreadLocal |
|---|---|---|
| 作用域 | 类或方法作用域 | 线程作用域 |
| 线程安全 | 需要同步机制 | 天然线程安全 |
| 数据共享 | 所有线程共享 | 每个线程独立 |
| 内存 | 一份数据 | 每个线程一份数据 |
| 使用场景 | 单线程或需要共享 | 多线程且需要隔离 |
详细对比:
1. 数据共享:
// 普通变量:所有线程共享
public class NormalVariable {
private String data = "共享数据";
public void test() {
new Thread(() -> {
data = "线程1修改";
System.out.println(data); // 可能被其他线程修改
}).start();
new Thread(() -> {
data = "线程2修改";
System.out.println(data); // 可能被其他线程修改
}).start();
}
}
// ThreadLocal:每个线程独立
public class ThreadLocalVariable {
private ThreadLocal<String> data = new ThreadLocal<>();
public void test() {
new Thread(() -> {
data.set("线程1的数据");
System.out.println(data.get()); // 总是:线程1的数据
}).start();
new Thread(() -> {
data.set("线程2的数据");
System.out.println(data.get()); // 总是:线程2的数据
}).start();
}
}
2. 线程安全:
// 普通变量:需要同步
private int count = 0;
public void increment() {
synchronized (this) { // 需要同步
count++;
}
}
// ThreadLocal:天然线程安全
private ThreadLocal<Integer> count = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0;
}
};
public void increment() {
count.set(count.get() + 1); // 不需要同步
}
总结: ThreadLocal提供线程局部变量,每个线程有独立副本,天然线程安全;普通变量所有线程共享,需要同步机制保证线程安全。
3. ThreadLocal的实现原理是什么?
完整答案:
ThreadLocal的实现原理:
ThreadLocal通过ThreadLocalMap实现,每个Thread对象内部有一个ThreadLocalMap,存储该线程的所有ThreadLocal变量。
核心原理:
Thread
└── ThreadLocalMap (每个线程一个)
└── Entry[] (数组,存储ThreadLocal变量)
└── Entry(ThreadLocal<?> k, Object v)
源码分析:
// Thread类中有ThreadLocalMap
public class Thread implements Runnable {
ThreadLocal.ThreadLocalMap threadLocals = null; // 线程的ThreadLocalMap
}
// ThreadLocal.get()
public T get() {
Thread t = Thread.currentThread(); // 1. 获取当前线程
ThreadLocalMap map = getMap(t); // 2. 获取该线程的Map
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this); // 3. 从该线程的Map获取值
if (e != null) {
return (T) e.value;
}
}
return setInitialValue();
}
// ThreadLocal.set()
public void set(T value) {
Thread t = Thread.currentThread(); // 1. 获取当前线程
ThreadLocalMap map = getMap(t); // 2. 获取该线程的Map
if (map != null) {
map.set(this, value); // 3. 存储到该线程的Map
} else {
createMap(t, value); // 4. 创建该线程的Map
}
}
总结: ThreadLocal通过ThreadLocalMap实现线程隔离,每个线程有独立的ThreadLocalMap存储ThreadLocal变量,实现线程局部变量。
4. ThreadLocal如何实现线程隔离?
完整答案:
线程隔离的实现机制:
ThreadLocal通过每个线程独立的ThreadLocalMap实现线程隔离,每个线程存储自己的数据副本。
隔离机制:
线程1
└── ThreadLocalMap1
└── Entry[ThreadLocal1 → "线程1的数据"]
线程2
└── ThreadLocalMap2
└── Entry[ThreadLocal1 → "线程2的数据"]
主线程
└── ThreadLocalMap3
└── Entry[ThreadLocal1 → "主线程的数据"]
源码分析:
// ThreadLocal.get()
public T get() {
Thread t = Thread.currentThread(); // 1. 获取当前线程
ThreadLocalMap map = getMap(t); // 2. 获取该线程的Map
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this); // 3. 从该线程的Map获取值
if (e != null) {
return (T) e.value;
}
}
return setInitialValue();
}
// ThreadLocalMap存储在Thread对象中
ThreadLocalMap getMap(Thread t) {
return t.threadLocals; // 返回该线程的Map
}
代码示例:
// 创建ThreadLocal
ThreadLocal<String> threadLocal = new ThreadLocal<>();
// 线程1
Thread thread1 = new Thread(() -> {
threadLocal.set("线程1的数据");
System.out.println("线程1: " + threadLocal.get());
// 输出:线程1: 线程1的数据
});
thread1.start();
// 线程2
Thread thread2 = new Thread(() -> {
threadLocal.set("线程2的数据");
System.out.println("线程2: " + threadLocal.get());
// 输出:线程2: 线程2的数据
});
thread2.start();
// 每个线程有自己独立的数据,互不干扰
关键点:
-
每个线程独立的Map:
- Thread.threadLocals存储该线程的所有ThreadLocal变量
- 不同线程的Map互不干扰
-
通过Thread.currentThread()获取:
- 总是获取当前线程的Map
- 确保线程隔离
-
数据不共享:
- 每个线程的数据存储在各自的Map中
- 线程间无法访问对方的数据
实际应用场景:
场景1:存储用户信息
public class UserContext {
private static ThreadLocal<User> userContext = new ThreadLocal<>();
public static void setUser(User user) {
userContext.set(user); // 设置到当前线程
}
public static User getUser() {
return userContext.get(); // 从当前线程获取
}
public static void clear() {
userContext.remove(); // 从当前线程移除
}
}
// 使用
// 线程1
new Thread(() -> {
UserContext.setUser(user1); // 线程1的数据
User user = UserContext.getUser(); // 获取线程1的数据
}).start();
// 线程2
new Thread(() -> {
UserContext.setUser(user2); // 线程2的数据
User user = UserContext.getUser(); // 获取线程2的数据
}).start();
场景2:Looper中的使用
// Android中Looper使用ThreadLocal实现线程绑定
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
// 为当前线程创建Looper
sThreadLocal.set(new Looper(quitAllowed));
}
public static Looper myLooper() {
// 获取当前线程的Looper
return sThreadLocal.get();
}
总结: ThreadLocal通过每个线程独立的ThreadLocalMap实现线程隔离。每个线程有自己的Map,存储该线程的所有ThreadLocal变量。通过Thread.currentThread()确保获取到正确的线程Map,实现线程间数据互不干扰。
5. ThreadLocal的内存泄漏问题
完整答案:
内存泄漏的原因:
ThreadLocal可能导致内存泄漏的原因是Entry的value是强引用,即使ThreadLocal被回收,value仍然被引用,无法被GC回收。
泄漏机制:
ThreadLocal (弱引用)
↓
ThreadLocalMap.Entry
├── key: ThreadLocal (弱引用) ✅ 可以被回收
└── value: Object (强引用) ❌ 无法被回收
泄漏场景:
// 场景:ThreadLocal使用完后没有remove()
public class MemoryLeakExample {
private static ThreadLocal<Context> contextLocal = new ThreadLocal<>();
public void setContext(Context context) {
contextLocal.set(context); // 设置值
}
public Context getContext() {
return contextLocal.get(); // 获取值
}
// ❌ 问题:忘记remove()
// ThreadLocal被回收,但value(Context)仍然被引用
// 如果Context持有Activity引用,Activity无法被回收 → 内存泄漏
}
解决方案:
方案1:使用完后及时remove()
public class SafeContextManager {
private static ThreadLocal<Context> contextLocal = new ThreadLocal<>();
public void setContext(Context context) {
contextLocal.set(context);
}
public Context getContext() {
return contextLocal.get();
}
// ✅ 使用完后清理
public void clearContext() {
contextLocal.remove(); // 清理ThreadLocal
}
// 或者在finally中清理
public void processWithContext(Context context) {
try {
contextLocal.set(context);
// 使用context
} finally {
contextLocal.remove(); // 确保清理
}
}
}
方案2:在线程池中使用时特别注意
// 线程池中的线程会复用,需要特别注意清理
ExecutorService executor = Executors.newFixedThreadPool(5);
executor.execute(() -> {
try {
threadLocal.set("数据");
// 使用数据
} finally {
threadLocal.remove(); // ✅ 必须清理
}
});
ThreadLocal的清理机制:
// ThreadLocalMap中有清理机制(expungeStaleEntry)
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// 1. 清理指定位置的Entry
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// 2. 继续清理后面的Entry(重新hash)
// ...
}
最佳实践:
- 及时清理:
// ✅ 推荐:使用try-finally确保清理
public void useThreadLocal() {
try {
threadLocal.set("数据");
// 使用数据
} finally {
threadLocal.remove(); // 确保清理
}
}
- 使用initialValue():
// ✅ 推荐:设置默认值
private static ThreadLocal<String> threadLocal = new ThreadLocal<String>() {
@Override
protected String initialValue() {
return ""; // 默认值
}
};
- 线程池中特别注意:
// ✅ 线程池中必须清理
executor.execute(() -> {
try {
threadLocal.set("数据");
// 使用
} finally {
threadLocal.remove(); // 必须清理
}
});
总结: ThreadLocal的内存泄漏主要是由于Entry的value是强引用导致的。解决方案:使用完后及时remove()、在finally中清理、在线程池中特别注意清理。ThreadLocalMap有清理机制,但需要手动触发。
2.2 ThreadLocalMap 深入理解
6. ThreadLocalMap的实现原理是什么?
完整答案:
ThreadLocalMap的定义:
ThreadLocalMap是ThreadLocal的内部类,是ThreadLocal的核心数据结构,用于存储线程的ThreadLocal变量。
数据结构:
// ThreadLocalMap使用数组存储Entry
static class ThreadLocalMap {
// Entry数组(类似HashMap的数组)
private Entry[] table;
private int size = 0;
private int threshold; // 扩容阈值
// Entry是键值对
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value; // 存储的值
Entry(ThreadLocal<?> k, Object v) {
super(k); // ThreadLocal作为key,使用弱引用
value = v; // value是强引用
}
}
}
核心特点:
-
数组结构:
- 使用Entry[]数组存储数据
- 类似HashMap,但更简单
-
哈希算法:
- 使用ThreadLocal的hashCode计算索引
- 使用线性探测法解决冲突
-
弱引用:
- Entry的key(ThreadLocal)是弱引用
- Entry的value是强引用
存储结构:
Thread
└── threadLocals: ThreadLocalMap
└── table: Entry[]
├── [0] Entry(ThreadLocal1, "值1")
├── [1] Entry(ThreadLocal2, "值2")
├── [2] null
├── [3] Entry(ThreadLocal3, "值3")
└── ...
哈希算法:
// ThreadLocal的hashCode
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);
}
// ThreadLocalMap计算索引
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0); // 线性探测
}
冲突解决机制:
ThreadLocalMap使用线性探测法(开放地址法)解决哈希冲突:
// ThreadLocalMap.set()
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被回收(弱引用),替换过期Entry
replaceStaleEntry(key, value, i);
return;
}
}
// 找到空位置,插入新Entry
tab[i] = new Entry(key, value);
size++;
}
为什么使用线性探测法:
-
简单高效:
- 实现简单,不需要链表
- 内存连续,缓存友好
-
适合场景:
- ThreadLocal数量通常不多
- 冲突概率较低
-
避免额外内存:
- 不需要链表节点
- 节省内存
总结: ThreadLocalMap使用数组+线性探测法实现,Entry的key是弱引用,value是强引用。使用黄金分割数计算hashCode,通过线性探测解决冲突。
7. ThreadLocalMap.Entry为什么使用弱引用?
完整答案:
Entry的结构:
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k); // ThreadLocal作为key,使用弱引用
value = v; // value是强引用
}
}
为什么key使用弱引用:
-
防止内存泄漏:
- 如果ThreadLocal是强引用,即使ThreadLocal对象不再使用,也无法被GC回收
- 使用弱引用,ThreadLocal对象可以被GC回收
-
自动清理机制:
- 当ThreadLocal被回收后,Entry的key变为null
- ThreadLocalMap可以检测到过期Entry并清理
引用关系:
强引用链:
Thread → ThreadLocalMap → Entry[] → Entry → value (强引用)
弱引用链:
ThreadLocal (外部引用) → Entry.key (弱引用)
如果外部不再引用ThreadLocal:
ThreadLocal可以被GC回收 → Entry.key变为null → 可以清理Entry
内存泄漏场景:
// 场景:ThreadLocal使用完后,外部引用被清除
public class ThreadLocalExample {
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public void useThreadLocal() {
threadLocal.set("数据");
// 使用数据
}
// 如果threadLocal是强引用:
// ThreadLocal对象无法被GC回收
// Entry无法被清理 → 内存泄漏
// 如果threadLocal是弱引用(实际实现):
// ThreadLocal对象可以被GC回收
// Entry.key变为null → 可以清理Entry
}
为什么value是强引用:
// Entry的value是强引用
Entry(ThreadLocal<?> k, Object v) {
super(k); // key是弱引用
value = v; // value是强引用 ❌
}
问题:
- value是强引用,即使ThreadLocal被回收,value仍然无法被GC回收
- 这是ThreadLocal内存泄漏的根本原因
解决方案:
// ✅ 使用完后及时remove()
threadLocal.set("数据");
try {
// 使用数据
} finally {
threadLocal.remove(); // 清理value
}
清理机制:
// ThreadLocalMap.expungeStaleEntry()
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// 清理过期Entry
tab[staleSlot].value = null; // 清理value
tab[staleSlot] = null;
size--;
// 继续清理后面的Entry(重新hash)
// ...
}
总结: ThreadLocalMap.Entry的key使用弱引用,防止ThreadLocal对象无法被GC回收。但value是强引用,可能导致内存泄漏,需要及时调用remove()清理。
2.3 ThreadLocal 使用场景和方法
8. ThreadLocal的使用场景有哪些?
完整答案:
主要使用场景:
ThreadLocal适用于需要线程隔离的场景,每个线程需要独立的数据副本。
场景1:存储线程上下文信息
// 存储请求上下文
public class RequestContext {
private static ThreadLocal<Request> requestContext = new ThreadLocal<>();
public static void setRequest(Request request) {
requestContext.set(request);
}
public static Request getRequest() {
return requestContext.get();
}
public static void clear() {
requestContext.remove();
}
}
// 使用
// 线程1处理请求1
new Thread(() -> {
RequestContext.setRequest(request1);
processRequest(); // 可以随时获取request1
}).start();
// 线程2处理请求2
new Thread(() -> {
RequestContext.setRequest(request2);
processRequest(); // 可以随时获取request2
}).start();
场景2:存储用户信息
// 存储当前用户信息
public class UserContext {
private static ThreadLocal<User> userContext = new ThreadLocal<>();
public static void setUser(User user) {
userContext.set(user);
}
public static User getUser() {
return userContext.get();
}
public static void clear() {
userContext.remove();
}
}
// 使用
// 每个请求线程存储自己的用户信息
public void handleRequest(Request request) {
User user = authenticate(request);
UserContext.setUser(user);
try {
// 在处理过程中可以随时获取用户信息
processRequest();
} finally {
UserContext.clear(); // 清理
}
}
场景3:存储数据库连接
// 存储数据库连接(每个线程一个连接)
public class DatabaseManager {
private static ThreadLocal<Connection> connectionContext = new ThreadLocal<>();
public static Connection getConnection() {
Connection conn = connectionContext.get();
if (conn == null) {
conn = createConnection();
connectionContext.set(conn);
}
return conn;
}
public static void closeConnection() {
Connection conn = connectionContext.get();
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
connectionContext.remove();
}
}
}
场景4:Android中Looper的使用
// Android中Looper使用ThreadLocal实现线程绑定
public final class Looper {
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
public static Looper myLooper() {
return sThreadLocal.get();
}
}
场景5:存储日期格式化器
// SimpleDateFormat不是线程安全的,使用ThreadLocal
public class DateFormatter {
private static ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
public static String format(Date date) {
return formatter.get().format(date);
}
}
适用场景总结:
| 场景 | 是否适用 | 说明 |
|---|---|---|
| 线程上下文信息 | ✅ 适用 | 每个线程需要独立的上下文 |
| 用户会话信息 | ✅ 适用 | 每个线程需要独立的用户信息 |
| 数据库连接 | ✅ 适用 | 每个线程需要独立的连接 |
| 日期格式化器 | ✅ 适用 | 避免线程安全问题 |
| 全局变量 | ❌ 不适用 | 需要所有线程共享 |
| 需要线程间通信 | ❌ 不适用 | ThreadLocal是隔离的,不能通信 |
不适用场景:
-
需要线程间共享数据:
- ThreadLocal是隔离的,不能用于线程间通信
-
需要全局变量:
- 应该使用普通变量或单例
-
需要传递参数:
- 应该使用方法参数传递
总结: ThreadLocal适用于需要线程隔离的场景,如线程上下文、用户信息、数据库连接、日期格式化器等。不适合需要线程间共享或通信的场景。
9. ThreadLocal.remove()的作用是什么?为什么必须调用?
完整答案:
remove()的作用:
remove()方法用于清除当前线程的ThreadLocal变量,释放内存,防止内存泄漏。
方法定义:
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null) {
m.remove(this); // 从ThreadLocalMap中移除Entry
}
}
为什么必须调用:
-
防止内存泄漏:
- Entry的value是强引用,即使ThreadLocal被回收,value仍然无法被GC回收
- 必须调用remove()清理value
-
线程池场景:
- 线程池中的线程会复用
- 如果不清理,上一个任务的数据会污染下一个任务
-
及时释放内存:
- ThreadLocalMap会一直持有value的引用
- 及时清理可以释放内存
内存泄漏示例:
// ❌ 错误:没有调用remove()
public class MemoryLeakExample {
private static ThreadLocal<Context> contextLocal = new ThreadLocal<>();
public void processRequest(Context context) {
contextLocal.set(context); // 设置值
// 使用context
// ❌ 忘记remove(),value无法被GC回收
}
}
// ✅ 正确:调用remove()
public class SafeExample {
private static ThreadLocal<Context> contextLocal = new ThreadLocal<>();
public void processRequest(Context context) {
try {
contextLocal.set(context);
// 使用context
} finally {
contextLocal.remove(); // ✅ 确保清理
}
}
}
线程池中的问题:
// ❌ 问题:线程池中不清理会导致数据污染
ExecutorService executor = Executors.newFixedThreadPool(5);
ThreadLocal<String> threadLocal = new ThreadLocal<>();
executor.execute(() -> {
threadLocal.set("任务1的数据");
// 处理任务1
// ❌ 没有remove(),数据残留在线程中
});
executor.execute(() -> {
// 同一个线程处理任务2
String data = threadLocal.get(); // ❌ 可能获取到任务1的数据
// 处理任务2
});
// ✅ 正确:使用finally清理
executor.execute(() -> {
try {
threadLocal.set("任务1的数据");
// 处理任务1
} finally {
threadLocal.remove(); // ✅ 必须清理
}
});
最佳实践:
// ✅ 方式1:使用try-finally
public void useThreadLocal() {
try {
threadLocal.set("数据");
// 使用数据
} finally {
threadLocal.remove(); // 确保清理
}
}
// ✅ 方式2:在方法结束时清理
public void processRequest() {
threadLocal.set("数据");
try {
// 使用数据
} finally {
threadLocal.remove(); // 清理
}
}
// ✅ 方式3:使用工具类封装
public class ThreadLocalManager {
public static <T> void withThreadLocal(ThreadLocal<T> threadLocal, T value, Runnable task) {
try {
threadLocal.set(value);
task.run();
} finally {
threadLocal.remove();
}
}
}
// 使用
ThreadLocalManager.withThreadLocal(threadLocal, "数据", () -> {
// 使用数据,自动清理
});
源码分析:
// ThreadLocal.remove()
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null) {
m.remove(this); // 从Map中移除Entry
}
}
// ThreadLocalMap.remove()
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); // 清理过期Entry
return;
}
}
}
总结: ThreadLocal.remove()用于清除当前线程的ThreadLocal变量,防止内存泄漏。在finally中调用remove()是最佳实践,特别是在线程池场景中必须清理。
10. ThreadLocal.initialValue()的作用是什么?
完整答案:
initialValue()的作用:
initialValue()方法用于设置ThreadLocal的初始值,当第一次调用get()时,如果值不存在,会调用initialValue()获取初始值。
方法定义:
protected T initialValue() {
return null; // 默认返回null
}
使用方式:
// 方式1:重写initialValue()
ThreadLocal<String> threadLocal = new ThreadLocal<String>() {
@Override
protected String initialValue() {
return "默认值"; // 设置初始值
}
};
// 使用
String value = threadLocal.get(); // 返回"默认值",不需要先set()
// 方式2:使用withInitial()(Java 8+)
ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "默认值");
使用场景:
场景1:设置默认值
// 计数器,初始值为0
ThreadLocal<Integer> counter = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0; // 初始值为0
}
};
// 使用
counter.set(counter.get() + 1); // 不需要先判断是否为null
场景2:日期格式化器
// 日期格式化器,每个线程一个
ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
// 使用
String dateStr = formatter.get().format(new Date()); // 直接使用,不需要先创建
场景3:避免空指针异常
// 设置默认值,避免null
ThreadLocal<String> name = new ThreadLocal<String>() {
@Override
protected String initialValue() {
return "未知"; // 默认值
}
};
// 使用
String userName = name.get(); // 不会返回null,返回"未知"
if (!userName.equals("未知")) { // 不需要null检查
// 处理
}
与set()的区别:
// 方式1:使用initialValue()
ThreadLocal<String> threadLocal = new ThreadLocal<String>() {
@Override
protected String initialValue() {
return "默认值";
}
};
String value = threadLocal.get(); // 返回"默认值"
// 方式2:使用set()
ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("默认值");
String value = threadLocal.get(); // 返回"默认值"
优势:
-
延迟初始化:
- 只有在第一次get()时才创建对象
- 节省内存
-
避免空指针:
- 总是有值,不需要null检查
-
代码简洁:
- 不需要先set()再get()
源码分析:
// ThreadLocal.get()
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
return (T) e.value;
}
}
return setInitialValue(); // 如果不存在,调用setInitialValue()
}
// ThreadLocal.setInitialValue()
private T setInitialValue() {
T value = initialValue(); // 调用initialValue()获取初始值
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
return value;
}
总结: ThreadLocal.initialValue()用于设置ThreadLocal的初始值,当第一次get()时自动调用。适合设置默认值、延迟初始化、避免空指针等场景。
2.4 InheritableThreadLocal
11. InheritableThreadLocal是什么?它和ThreadLocal的区别是什么?
完整答案:
InheritableThreadLocal的定义:
InheritableThreadLocal是ThreadLocal的子类,它允许子线程继承父线程的ThreadLocal变量。
主要区别:
| 特性 | ThreadLocal | InheritableThreadLocal |
|---|---|---|
| 继承性 | 子线程无法继承父线程的值 | 子线程可以继承父线程的值 |
| 使用场景 | 线程隔离 | 父子线程传递数据 |
| 实现方式 | ThreadLocalMap | InheritableThreadLocalMap |
代码示例:
// ThreadLocal:子线程无法继承
ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("父线程的数据");
new Thread(() -> {
String value = threadLocal.get(); // 返回null
System.out.println(value);
}).start();
// InheritableThreadLocal:子线程可以继承
InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
inheritableThreadLocal.set("父线程的数据");
new Thread(() -> {
String value = inheritableThreadLocal.get(); // 返回"父线程的数据"
System.out.println(value);
}).start();
实现原理:
// InheritableThreadLocal
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
protected T childValue(T parentValue) {
return parentValue; // 子线程继承父线程的值
}
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals; // 使用inheritableThreadLocals
}
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
// Thread类中有两个Map
public class Thread implements Runnable {
ThreadLocal.ThreadLocalMap threadLocals = null; // ThreadLocal使用
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; // InheritableThreadLocal使用
}
使用场景:
场景1:传递请求上下文
// 传递请求ID到子线程
InheritableThreadLocal<String> requestId = new InheritableThreadLocal<>();
// 主线程
requestId.set("request-123");
// 创建子线程处理请求
new Thread(() -> {
String id = requestId.get(); // 可以获取到"request-123"
processRequest(id);
}).start();
场景2:传递用户信息
// 传递用户信息到子线程
InheritableThreadLocal<User> userContext = new InheritableThreadLocal<>();
// 主线程
userContext.set(currentUser);
// 子线程可以获取用户信息
new Thread(() -> {
User user = userContext.get(); // 可以获取到currentUser
// 处理任务
}).start();
注意事项:
-
只在创建时继承:
- 只有在创建子线程时才会继承
- 如果父线程之后修改值,子线程不会看到
-
线程池问题:
- 线程池中的线程是复用的
- 可能获取到上一个任务的数据
-
内存泄漏:
- 同样需要调用remove()清理
总结: InheritableThreadLocal允许子线程继承父线程的ThreadLocal变量,适合父子线程传递数据的场景。但需要注意线程池复用和内存泄漏问题。
2.5 ThreadLocal 清理机制和最佳实践
12. ThreadLocalMap的清理机制(expungeStaleEntry)是什么?
完整答案:
清理机制的作用:
expungeStaleEntry()是ThreadLocalMap的核心清理方法,用于清理过期的Entry(key为null的Entry),防止内存泄漏。
过期Entry的产生:
// Entry的key是弱引用
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value; // value是强引用
Entry(ThreadLocal<?> k, Object v) {
super(k); // key是弱引用
value = v; // value是强引用
}
}
// 当ThreadLocal对象被GC回收后
ThreadLocal threadLocal = new ThreadLocal<>();
threadLocal.set("数据");
threadLocal = null; // 外部引用被清除
// ThreadLocal对象被GC回收 → Entry.key变为null → Entry变成过期Entry
expungeStaleEntry()的实现:
// ThreadLocalMap.expungeStaleEntry()
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// 1. 清理指定位置的过期Entry
tab[staleSlot].value = null; // 清理value(强引用)
tab[staleSlot] = null; // 清理Entry
size--;
// 2. 重新hash后面的Entry(因为线性探测,后面的Entry可能受影响)
Entry e;
int i;
for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {
// 如果后面的Entry也是过期的,继续清理
e.value = null;
tab[i] = null;
size--;
} else {
// 如果后面的Entry不是过期的,重新计算位置
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; // 返回下一个空位置
}
清理时机:
- 调用remove()时:
// ThreadLocal.remove()
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null) {
m.remove(this); // 内部会调用expungeStaleEntry()
}
}
- set()时发现过期Entry:
// ThreadLocalMap.set()
private void set(ThreadLocal<?> key, Object value) {
// ...
for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == null) {
// 发现过期Entry,清理并替换
replaceStaleEntry(key, value, i); // 内部调用expungeStaleEntry()
return;
}
}
}
- get()时发现过期Entry:
// ThreadLocalMap.getEntry()
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); // 可能触发清理
}
}
清理流程:
1. 发现过期Entry(key为null)
↓
2. 调用expungeStaleEntry()
↓
3. 清理过期Entry的value
↓
4. 重新hash后面的Entry(因为线性探测)
↓
5. 继续清理后面的过期Entry
↓
6. 返回下一个空位置
为什么需要重新hash:
// 线性探测示例
// 初始状态:
// [0] Entry(ThreadLocal1, "值1")
// [1] Entry(ThreadLocal2, "值2") // ThreadLocal2的hashCode是1,但位置0被占用,所以放在位置1
// [2] null
// 如果ThreadLocal1被回收,位置0变成过期Entry:
// [0] Entry(null, "值1") // 过期Entry
// [1] Entry(ThreadLocal2, "值2")
// [2] null
// 清理位置0后,需要重新hash位置1的Entry:
// 检查ThreadLocal2的正确位置是否是1
// 如果是,保持不变
// 如果不是,移动到正确位置
总结: expungeStaleEntry()用于清理过期Entry,防止内存泄漏。它会清理value、重新hash后面的Entry,确保线性探测的正确性。在remove()、set()、get()时都可能触发清理。
13. ThreadLocal在线程池中的正确使用方式是什么?
完整答案:
线程池中的问题:
线程池中的线程是复用的,如果不清理ThreadLocal,上一个任务的数据会污染下一个任务。
问题示例:
// ❌ 错误:线程池中不清理ThreadLocal
ExecutorService executor = Executors.newFixedThreadPool(5);
ThreadLocal<String> threadLocal = new ThreadLocal<>();
executor.execute(() -> {
threadLocal.set("任务1的数据");
// 处理任务1
// ❌ 没有remove(),数据残留
});
// 同一个线程处理任务2
executor.execute(() -> {
String data = threadLocal.get(); // ❌ 可能获取到"任务1的数据"
// 处理任务2,数据被污染
});
正确使用方式:
方式1:使用try-finally清理(推荐)
ExecutorService executor = Executors.newFixedThreadPool(5);
ThreadLocal<String> threadLocal = new ThreadLocal<>();
executor.execute(() -> {
try {
threadLocal.set("任务1的数据");
// 处理任务1
} finally {
threadLocal.remove(); // ✅ 必须清理
}
});
方式2:封装工具类
// 封装ThreadLocal工具类
public class ThreadLocalManager {
public static <T> void withThreadLocal(ThreadLocal<T> threadLocal, T value, Runnable task) {
try {
threadLocal.set(value);
task.run();
} finally {
threadLocal.remove(); // 自动清理
}
}
public static <T, R> R withThreadLocal(ThreadLocal<T> threadLocal, T value, Supplier<R> supplier) {
try {
threadLocal.set(value);
return supplier.get();
} finally {
threadLocal.remove(); // 自动清理
}
}
}
// 使用
ThreadLocal<String> threadLocal = new ThreadLocal<>();
executor.execute(() -> {
ThreadLocalManager.withThreadLocal(threadLocal, "数据", () -> {
// 处理任务,自动清理
});
});
方式3:使用ThreadLocal的initialValue()
// 使用initialValue()设置默认值
ThreadLocal<String> threadLocal = new ThreadLocal<String>() {
@Override
protected String initialValue() {
return ""; // 默认值
}
};
// 但这样仍然需要清理,因为可能设置了非默认值
executor.execute(() -> {
try {
threadLocal.set("任务数据");
// 处理任务
} finally {
threadLocal.remove(); // ✅ 仍然需要清理
}
});
InheritableThreadLocal在线程池中的问题:
// ❌ 问题:InheritableThreadLocal在线程池中会继承上一个任务的数据
InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
// 主线程
inheritableThreadLocal.set("主线程数据");
// 线程池中的线程可能获取到上一个任务的数据
executor.execute(() -> {
String data = inheritableThreadLocal.get(); // ❌ 可能获取到上一个任务的数据
// 处理任务
});
// ✅ 解决:必须清理
executor.execute(() -> {
try {
// 处理任务
} finally {
inheritableThreadLocal.remove(); // ✅ 必须清理
}
});
最佳实践:
- 总是使用try-finally:
executor.execute(() -> {
try {
threadLocal.set("数据");
// 处理任务
} finally {
threadLocal.remove(); // ✅ 确保清理
}
});
- 封装工具类:
// 封装后使用更安全
ThreadLocalManager.withThreadLocal(threadLocal, "数据", () -> {
// 处理任务
});
- 避免在长时间运行的线程中使用:
// ❌ 避免:在长时间运行的线程中累积数据
Thread workerThread = new Thread(() -> {
while (true) {
threadLocal.set("数据");
// 处理任务
// ❌ 没有清理,数据累积
}
});
// ✅ 推荐:每个任务都清理
Thread workerThread = new Thread(() -> {
while (true) {
try {
threadLocal.set("数据");
// 处理任务
} finally {
threadLocal.remove(); // ✅ 每个任务都清理
}
}
});
总结: ThreadLocal在线程池中必须使用try-finally清理,防止数据污染。可以封装工具类简化使用。InheritableThreadLocal在线程池中也需要清理。