问题
什么是ThreadLocal,如何实现的?
答案
核心概念
ThreadLocal(线程局部变量)是一种线程隔离机制,为每个使用该变量的线程提供独立的变量副本,各线程之间互不干扰。
核心特性:
- 每个线程都有自己独立的变量副本
- 线程之间数据隔离,无需同步
- 线程结束时,其ThreadLocal变量会被自动回收
实现原理
核心设计思想:
- 变量不是存储在ThreadLocal对象中,而是存储在每个Thread对象内部的ThreadLocalMap中
- ThreadLocal充当”key”的角色,通过它在当前线程的Map中存取值
关键数据结构:
public class Thread {
// 每个线程都有一个ThreadLocalMap实例
ThreadLocal.ThreadLocalMap threadLocals = null;
}
public class ThreadLocal<T> {
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = t.threadLocals; // 获取当前线程的Map
if (map != null)
map.set(this, value); // this作为key,存储value
else
createMap(t, value); // 首次使用时创建Map
}
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = t.threadLocals;
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this); // 以this为key查找
if (e != null)
return (T)e.value;
}
return setInitialValue(); // 未设置时返回初始值(默认null)
}
public void remove() {
ThreadLocalMap m = Thread.currentThread().threadLocals;
if (m != null)
m.remove(this);
}
}
ThreadLocalMap内部结构
Entry结构:
static class ThreadLocalMap {
// Entry继承WeakReference,key是ThreadLocal的弱引用
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k); // key为弱引用
value = v; // value为强引用
}
}
private Entry[] table; // 使用数组存储,非链表
private int size;
private int threshold; // 扩容阈值(capacity * 2/3)
// 初始容量16,必须是2的幂次
private static final int INITIAL_CAPACITY = 16;
}
哈希计算:
// ThreadLocal使用自增ID作为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);
}
// 计算索引位置
int i = key.threadLocalHashCode & (len - 1);
魔数0x61c88647的作用:
- 斐波那契散列,能够在2的幂次容量下均匀分布,减少哈希冲突
- 即使连续创建多个ThreadLocal,也能避免聚集
冲突解决:线性探测法
ThreadLocalMap使用开放地址法(线性探测)而非链表法:
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len - 1); // 计算初始位置
// 线性探测:向后查找空位或匹配的key
for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value; // 找到相同key,更新value
return;
}
if (k == null) {
replaceStaleEntry(key, value, i); // key已被GC,替换
return;
}
}
tab[i] = new Entry(key, value); // 找到空位,插入
size++;
if (!cleanSomeSlots(i, size) && size >= threshold)
rehash(); // 清理后仍超过阈值,则扩容
}
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0); // 循环递增
}
为什么使用弱引用
Entry的key为何设计为WeakReference:
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
}
原因:
- 如果key是强引用,当外部ThreadLocal对象置为null时,Thread对象仍持有强引用,导致ThreadLocal无法被GC
- 使用弱引用后,即使Thread存活,ThreadLocal对象也能在下次GC时被回收
流程图:
外部ThreadLocal引用 --> ThreadLocal对象 <-- WeakReference(key)
↑
ThreadLocalMap.Entry
↓
value
当外部ThreadLocal引用置为null后:
- 下次GC时,ThreadLocal对象被回收
- Entry的key变为null(弱引用特性)
- ThreadLocalMap在set/get/remove时会清理key为null的Entry
完整执行流程示例
public class ThreadLocalDemo {
// 1. 创建ThreadLocal(静态变量,所有线程共享同一个ThreadLocal对象)
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
// 主线程设置值
threadLocal.set("主线程Value");
System.out.println("主线程读取: " + threadLocal.get());
// 子线程1
new Thread(() -> {
threadLocal.set("子线程1Value");
System.out.println("子线程1读取: " + threadLocal.get());
threadLocal.remove(); // 及时清理,避免内存泄漏
}).start();
// 子线程2
new Thread(() -> {
System.out.println("子线程2读取: " + threadLocal.get()); // null
}).start();
}
}
// 输出:
// 主线程读取: 主线程Value
// 子线程1读取: 子线程1Value
// 子线程2读取: null
执行过程:
- 主线程执行
threadLocal.set("主线程Value"):- 获取主线程的Thread对象
- 创建主线程的ThreadLocalMap(首次使用)
- 将Entry(threadLocal, “主线程Value”)存入Map
- 子线程1执行
threadLocal.set("子线程1Value"):- 获取子线程1的Thread对象
- 创建子线程1的ThreadLocalMap
- 存储独立的值,不影响主线程
- 子线程2执行
threadLocal.get():- 未设置过值,返回null(或initialValue)
关键源码解析
初始值设置:
public class ThreadLocal<T> {
protected T initialValue() {
return null; // 默认返回null
}
// JDK8新增:Lambda方式设置初始值
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}
}
// 使用示例
ThreadLocal<SimpleDateFormat> dateFormat = ThreadLocal.withInitial(
() -> new SimpleDateFormat("yyyy-MM-dd")
);
面试答题要点
- 设计原理:变量存储在Thread对象的ThreadLocalMap中,ThreadLocal只是作为key
- 线程隔离:每个线程有独立的ThreadLocalMap,实现数据隔离,无需同步
- 底层结构:ThreadLocalMap使用Entry数组,通过线性探测法解决冲突
- 弱引用设计:Entry的key为WeakReference,防止ThreadLocal对象无法回收
- 使用规范:必须调用remove()清理,避免内存泄漏(尤其在线程池场景)
- 适用场景:数据库连接、用户会话、SimpleDateFormat等需要线程隔离的对象