ThreadLocal源码分析
1.代码分析
每个线程类(Thread.class)都会有一个自己的ThreadLocals,即
ThreadLocal.ThreadLocalMap threadLocals = null;
而一个ThreadLocalMap,存储了一个包含Entry对象的数组 table:
Entry 继承了弱引用 WeakReference --> set时会将key置为弱引用,而value则是强引用。
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
/**
* The initial capacity -- MUST be a power of two.
*/
private static final int INITIAL_CAPACITY = 16;
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
....
}
我们可以在程序中设置多个ThreadLocal<T> 对象作为key,如
private static final ThreadLocal<AgentContext> threadLocal = new ThreadLocal<>(); private static final ThreadLocal<String> instance = new ThreadLocal();
这些ThreadLocal对象将作为key,用来存储或获取ThreadLocalMap中的entry。其中会通过
int i = key.threadLocalHashCode & (table.length - 1);
为每个key去设置固定的数组下标,在设置或清除时对指定位置的value进行覆盖/清除。
get方法
-
找到线程的
threadLocals。 -
用
threadLocalHashCode & (len-1)定位槽位。 -
找到 Entry 就返回 value。
-
没找到则调用
initialValue()并 set 进 map。
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t); // 获取当前线程的 ThreadLocalMap
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this); // 用 key 查找 Entry
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value; // 返回 value
return result;
}
}
return setInitialValue(); // 没有则初始化
}
set方法
-
覆盖已有 value。
-
遇到 stale entry 会替换并清理。
-
如果数组满了,可能扩容。
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)]) {
if (e.refersTo(key)) { // 找到旧 Entry
e.value = value; // 覆盖 value
return;
}
if (e.refersTo(null)) { // 遇到 stale entry
replaceStaleEntry(key, value, i); // 替换并清理
return;
}
}
tab[i] = new Entry(key, value); // 没有旧 Entry,直接插入
size++;
if (!cleanSomeSlots(i, size) && size >= threshold)
rehash(); // 扩容或清理
}
remove方法
-
只清理指定 ThreadLocal 对应的 Entry。
-
结合
expungeStaleEntry()避免线程池长期持有 value 导致内存泄漏。
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.refersTo(key)) { // 找到对应 Entry
e.clear(); // 清空 key
expungeStaleEntry(i); // 清理后续 stale entry
return;
}
}
}
2.ThreadLocal 内存占用机制总结(含 static final 情况)
-
ThreadLocalMap 结构
-
每个线程维护一个
ThreadLocalMap,存储线程对应的 ThreadLocal 对象。 -
Entry:
key是 ThreadLocal 的弱引用,value是线程持有的强引用对象(ex:AgentContext)。
-
-
弱引用 key 的作用
-
当 ThreadLocal 对象本身不再被引用时,弱引用 key 可以被 GC 回收。
-
Entry 会变成 stale entry(key 为 null),但 value 仍存在 map 中。
-
-
潜在的内存占用问题
-
普通 ThreadLocal:如果线程长期存在(例如线程池线程复用),stale entry 的 value 会长期占用内存。
-
static final ThreadLocal:ThreadLocal 对象本身一直有强引用,不会被回收。
-
key 永远不会变成 null,Entry 永远有效。
-
因此,如果 value 没有被 remove(),线程存活期间一直被强引用占用。
-
不会出现 “stale entry” 情况,但线程池长期使用下仍需 remove 避免内存长期占用。
-
-
-
所以,不管是基于内存泄漏的风险,还是赋值污染的问题,都建议手动进行remove()
-
使用完 ThreadLocal 后,必须手动调用
remove(),尤其在线程池等长期存活线程中。static final ThreadLocal 虽然不会变成 stale entry,但 remove() 仍是释放 value 内存的必要操作。
-
更多推荐

所有评论(0)