乐于分享
好东西不私藏

Java ThreadLocal 源码分析

Java ThreadLocal 源码分析

ThreadLocal 是 Java 提供的一种线程局部变量机制,它为每个使用该变量的线程提供独立的副本,使得每个线程都可以独立地修改自己的副本而不会影响其他线程,从而天然避免了多线程环境下的数据竞争问题。

核心方法

方法 说明
T get()
返回当前线程所对应的线程局部变量的值。如果尚未设置,则调用 initialValue() 初始化并返回。
void set(T value)
将当前线程的线程局部变量设置为指定值。
void remove()
移除当前线程的线程局部变量值。在线程池等复用线程的场景中,必须显式调用remove(),否则可能造成数据污染或内存泄漏。
protected T initialValue()
返回该线程局部变量的初始值。默认返回 null。通常通过匿名内部类或 lambda 表达式重写此方法来提供默认值。

简单使用示例

publicclass ThreadLocalExample {private static final ThreadLocal<String> context = new ThreadLocal<>();public static void main(String[] args) throws InterruptedException {Runnable task = () -> {String threadName = Thread.currentThread().getName();// 设置线程局部变量            context.set("User-" + threadName);            System.out.println(threadName + " - 设置值: " + context.get());try {// 模拟业务逻辑(可能抛出异常)                doSomething();            } finally {// 关键:无论是否异常,都清除 ThreadLocal                context.remove();                System.out.println(threadName + " - 已清理 ThreadLocal");            }// 验证是否已清除(应为 null)            System.out.println(threadName + " - 清理后 get(): " + context.get());        };Thread t1 new Thread(task, "Thread-1");Thread t2 new Thread(task, "Thread-2");        t1.start();        t2.start();        t1.join();        t2.join();        System.out.println("Main thread - get(): " + context.get()); // null    }private static void doSomething() {String user = context.get();if (user != null) {            System.out.println("  [doSomething] 当前用户: " + user);// 模拟可能的异常// throw new RuntimeException("Oops!");// 模拟耗时工作try {                Thread.sleep(10);            } catch (InterruptedException e) {            }        }    }}

输出:

Thread-1 - 设置值: User-Thread-1  [doSomething] 当前用户: User-Thread-1Thread-2 - 设置值: User-Thread-2  [doSomething] 当前用户: User-Thread-2Thread-1 - 已清理 ThreadLocalThread-1 - 清理后 get(): nullThread-2 - 已清理 ThreadLocalThread-2 - 清理后 get(): nullMain thread - get(): null

源码分析

JDK 8 ThreadLocal 源码如下:

// Thread 类:每个线程对象内部持有一个 ThreadLocalMappublic class Thread implements Runnable {/*      * threadLocals 是当前线程私有的 ThreadLocalMap。     * 它由 ThreadLocal 类负责维护(创建、读写),Thread 本身不操作它。     * 初始值为 null,只有当线程第一次使用 ThreadLocal 时才会被初始化。     */    ThreadLocal.ThreadLocalMap threadLocals null;}// ThreadLocal 类:为每个线程提供独立的变量副本public class ThreadLocal<T> {/**     * 获取当前线程中与该 ThreadLocal 实例关联的值。     * 如果尚未设置,则调用 setInitialValue() 初始化。     */public T get() {// 1. 获取当前正在执行的线程Thread = Thread.currentThread();// 2. 从当前线程中获取其持有的 ThreadLocalMap(即 threadLocals 字段)        ThreadLocal.ThreadLocalMap map = getMap(t);// 3. 如果 map 存在(即该线程至少使用过一个 ThreadLocal)if (map != null) {// 4. 以当前 ThreadLocal 实例(this)为 key,在 map 中查找对应的 Entry            ThreadLocal.ThreadLocalMap.Entry = map.getEntry(this);// 5. 如果找到了有效 Entry(未被回收且存在)if (e != null) {// 6. 取出 value 并强制转换为泛型类型 T(由于泛型擦除,需 unchecked 转换)@SuppressWarnings("unchecked")result = (T)e.value;return result;            }        }// 7. 如果 map 不存在 或 没有找到对应 entry,则进行初始化(通常返回 initialValue())return setInitialValue();    }/**     * 为当前线程设置该 ThreadLocal 的值。     */public void set(T value) {// 1. 获取当前线程Thread = Thread.currentThread();// 2. 尝试获取当前线程的 ThreadLocalMap        ThreadLocal.ThreadLocalMap map = getMap(t);// 3. 如果 map 已存在(线程已使用过 ThreadLocal)if (map != null)// 4. 直接将 (this, value) 存入 map(this 作为 key)            map.set(this, value);else// 5. 否则,首次使用,为该线程创建一个新的 ThreadLocalMap            createMap(t, value);    }/**     * 从指定线程中获取其 ThreadLocalMap(即 threadLocals 字段)。     * 这是 ThreadLocal 与 Thread 解耦的关键:ThreadLocal 不持有线程数据,而是从线程中“拉取”。     */    ThreadLocal.ThreadLocalMap getMap(Thread t) {return t.threadLocals; // 直接返回 Thread 对象的 threadLocals 成员    }// 其他省略...}// ThreadLocalMap:ThreadLocal 的内部静态类,一个定制化的哈希表,仅用于存储线程局部变量。// 使用弱引用(WeakReference)作为 key(即 ThreadLocal 实例),防止内存泄漏。static class ThreadLocalMap {/**     * 哈希表底层数组,存储 Entry。     */private Entry[] table;/**     * Entry 继承 WeakReference<ThreadLocal<?>>,     * 其 referent(父类字段)就是 ThreadLocal 实例(作为 key),而 value 是用户存储的对象。     * 关键设计:     * - key 是弱引用:当外部不再强引用某个 ThreadLocal 实例时,GC 可回收该 key。     * - value 是强引用:若不手动 remove,即使 key 被回收,value 仍可能滞留,造成内存泄漏。     */static class Entry extends WeakReference<ThreadLocal<?>> {/** 与该 ThreadLocal 关联的值 */        Object value;        Entry(ThreadLocal<?> k, Object v) {super(k);      // 将 k 作为弱引用的 referent            value = v;     // 强引用 value        }    }// getEntry/set 等核心逻辑包括:// - 使用线性探测法解决哈希冲突// - 在 get/set 时会清理部分 stale entries(陈旧条目),//   这是 ThreadLocalMap 内部一种被动式内存回收机制,用于缓解因 ThreadLocal 实例被回收后,//   其对应的 value 仍残留在 ThreadLocalMap 中造成的内存泄漏问题。// 以 getEntry 方法为例:private Entry getEntry(ThreadLocal<?> key) {// 1. 计算 key 的哈希槽位索引int = key.threadLocalHashCode & (table.length - 1);// 2. 获取该槽位上的 EntryEntry = table[i];// 3. 检查是否“命中”if (e != null && e.get() == key)return e; // 直接命中,返回 Entry// 4. 如果未命中(可能是 null、key 不匹配、或 key 已被回收),进入线性探测查找elsereturn getEntryAfterMiss(key, i, e);    }// 当在直接哈希槽位未找到 key 时,使用线性探测继续查找。// 同时在此过程中清理遇到的 stale entry(key == null 的条目)。private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {// 1. 获取当前 table 和长度(避免多次访问字段)        Entry[] tab = table;int len = tab.length;// 2. 线性探测循环:只要当前 Entry 不为 null,就继续while (e != null) {// 3. 获取当前 Entry 的 key(即 WeakReference 指向的 ThreadLocal 实例)            ThreadLocal<?> k = e.get();// 4. 情况一:找到目标 keyif (k == key)return e; // 返回匹配的 Entry// 5. 情况二:发现 stale entry(key 已被 GC 回收,k == null)if (k == null)// 触发清理:从索引 i 开始,删除该 stale entry,// 并 rehash 后续受影响的 entry(防止断链)                expungeStaleEntry(i);// 6. 情况三:key 存在但不匹配(哈希冲突),继续线性探测else                i = nextIndex(i, len); // 计算下一个槽位((i + 1) % len)// 7. 移动到下一个槽位            e = tab[i];        }// 8. 遇到 null 槽位,说明 key 不存在(开放寻址法约定:插入位置在第一个 null 处)return null;    }}

可以看到,每个 Thread 对象内部持有一个 ThreadLocal.ThreadLocalMap 类型的字段 threadLocals,初始为 null,首次使用 ThreadLocal 时才被初始化。这样就实现了“线程隔离”:不同线程操作的是各自独立的 map。

ThreadLocal 本身不存储值,而是把自身 (this)作为 key 存入当前线程的 ThreadLocal.ThreadLocalMap类型的字段 threadLocals。当调用 threadLocal.get() 时,实际是 Thread.currentThread().threadLocals.get(this)

ThreadLocal.ThreadLocalMap 并不是标准的 HashMap,而是 ThreadLocal 内部实现的专用哈希表。它使用 数组 + 开放寻址法(线性探测) 解决哈希冲突,而非链表或红黑树。

ThreadLocal.ThreadLocalMap 中的Entry 设计采用“弱引用 key + 强引用 value”的方式:Entry 继承自 WeakReference<ThreadLocal<?>>,其 referent 字段作为 key;value 字段则是普通对象引用(强引用)。当外部无强引用指向某个 ThreadLocal 实例时,GC 可回收其 key,此时 Entry.get() == null,称为 stale entry(陈旧条目)。使用弱引用 key 可以避免 ThreadLocal 实例因被 map 引用而无法回收;但 value 是强引用,若不显式调用 remove(),即使 key 被回收,value 仍可能长期驻留内存。

ThreadLocalMap 对这些 value 内存泄漏的防护(但非完全免疫)方式是“被动清理”:ThreadLocalMap在 get 或 set 操作进行线性探测查找时,会顺带清理路径上 key 已被回收(即 key == null)的陈旧条目(stale entry),以缓解内存泄漏,但该机制是被动且局部的,不能替代显式调用 remove()

本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » Java ThreadLocal 源码分析

评论 抢沙发

9 + 9 =
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
×
订阅图标按钮