一、序言
本文聊聊 ThreadLocal。
二、ThreadLocal 源码
package java.lang;
import jdk.internal.misc.TerminatingThreadLocal;
import java.lang.ref.*;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
public class ThreadLocal<T> {
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode = new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
public ThreadLocal() {
}
// 省略除成员变量和构造器之外的代码
}
上面是 ThreadLocal 的源码,省略了除成员变量和构造器之外的代码。当使用构造器实例化一个 ThreadLocal 对象时,除了成员变量 threadLocalHashCode、nextHashCode 与常量 HASH_INCREMENT 均有默认值就没有做其他的动作了。
三、ThreadLocal#set()
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程的 ThreadLocalMap
ThreadLocalMap map = getMap(t);
// 判断 ThreadLocalMap 是否存在
if (map != null) {
// 设置值到 ThreadLocalMap
map.set(this, value);
} else {
// 否则,创建新的 ThreadLocalMap 并设置值
createMap(t, value);
}
}
上面的是 ThreadLocal#set() 方法的源码。源码中有一个 getMap() 方法和 createMap() 方法:
ThreadLocalMap getMap(Thread t) {
// 通过调用当前线程的 threadLocals 成员变量获取 ThreadLocalMap 对象
return t.threadLocals;
}
void createMap(Thread t, T firstValue)
// 创建一个 ThreadLocalMap 对象,并将值放入
// 将新建的 ThreadLocalMap 对象赋给当前线程的 threadLocals 成员变量
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocal#set() 方法工作流程:

四、ThreadLocal#get()
public T get() {
// 获取了当前线程的引用
Thread t = Thread.currentThread();
// 获取了当前线程的 ThreadLocalMap 对象
ThreadLocalMap map = getMap(t);
// 检查当前线程是否有 ThreadLocalMap
if (map != null) {
// 从 ThreadLocalMap 中获取了一个 Entry 对象
// 获取 ThreadLocalMap 中,键为当前 ThreadLocal 的 Entry
ThreadLocalMap.Entry e = map.getEntry(this);
// 检查 Entry 对象是否存在
if (e != null) {
// 获取了 Entry 对象的值,并将其转型为 T 类型。
@SuppressWarnings("unchecked")
T result = (T)e.value;
// 返回了线程局部变量的值
return result;
}
}
// 设置一个初始值(这个初始值为 null),并返回
return setInitialValue();
}
上面是 ThreadLocal#get() 的源码。其工作流程如下:

五、ThreadLocal#remove()
public void remove() {
// 获取了当前线程的 ThreadLocalMap 对象
ThreadLocalMap m = getMap(Thread.currentThread());
// 判断当前线程 ThreadLocalMap 对象是否存在
if (m != null) {
// 删除 ThreadLocalMap 中键为当前 ThreadLocal 的数据
m.remove(this);
}
}
六、ThreadLocal 工作流程
6.1 ThreadLocal 数据存放流程

- 向 ThreadLocal 中存放数据时,首先会判断线程绑定的 ThreadLocalMap 是否存在
- 如果存在,将 ThreadLocal 作为 key 存入 ThreadLocalMap 中
- 如果不存在,则先创建 ThreadLocalMap,然后执行第 2 步的操作
6.2 ThreadLocal 数据获取流程

- 向 ThreadLocal 中获取数据时,首先会判断线程绑定的 ThreadLocalMap 是否存在
- 如果存在,将获取 ThreadLocal 为 key 的值
- 如果不存在,null 将会作为默认值返回(在这之前同样会新建 ThreadLocalMap)
七、FAQ
7.1 ThreadLocalMap#set()
private void set(ThreadLocal<?> key, Object value) {
// 获取了 ThreadLocalMap 的内部数组 table
// 这个数组存储了所有的 Entry 对象。
Entry[] tab = table;
// 获取 tab 的长度
int len = tab.length;
// 计算 ThreadLocal 的 hash 值,并将其映射到 table 的索引范围内
int i = key.threadLocalHashCode & (len-1);
// 循环遍历 tab
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
// 从 ThreadLocalMap 中的 Entry 对象 e 中获取 ThreadLocal 对象 k
ThreadLocal<?> k = e.get();
// 检查当前的 Entry 对象的键是否等于 ThreadLocal 对象
if (k == key) {
// 将 Entry 对象的值设置为新的值
e.value = value;
return;
}
// 检查当前的 Entry 对象的键是否为 null
if (k == null) {
// 调用 replaceStaleEntry 方法来替换这个 Entry 对象
replaceStaleEntry(key, value, i);
return;
}
}
// 在 table 中创建了一个新的 Entry 对象
// 键是 ThreadLocal 对象,值是新的值。
tab[i] = new Entry(key, value);
// Entry 对象的数量 +1
int sz = ++size;
// 检查 ThreadLocalMap 是否需要进行哈希表的再哈希操作
if (!cleanSomeSlots(i, sz) && sz >= threshold)
// 调用 rehash 方法来进行再哈希
rehash();
}
7.2 ThreadLocal 内存泄露
之前 ThreadLocalMap#set() 源码中有这么一行代码 tab[i] = new Entry(key, value),我们看看 Entry 对象究竟是什么。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
上述代码是 Entry 的源码。Entry 继承自 WeakReference,也就是说 Entry 是一个弱引用。ThreadLocal 的键是对 ThreadLocal 对象的弱引用,而值是强引用。这就导致了一个问题,ThreadLocal 在没有外部对象强引用时,发生 GC 时弱引用 key 会被回收,而 value 不会回收。如果线程没有结束,但 ThreadLocal 已经被回收,则可能导致线程中存在 ThreadLocalMap<null, Object> 的键值对,造成内存泄漏。
7.3 ThreadLocal 线程池问题
线程池中的核心线程一般是不会被销毁的。如果一个线程不销毁,那么跟随这个线程的 ThreadLocalMap 就一直存在,上次变量的变更,下次依然在使用。如果线程池中的一个线程执行了一次任务,而没有调用 remove() 方法,那么下次这个线程在执行任务的时候,就有可能会引发业务逻辑问题。所以,当不再需要 ThreadLocal 中的值进行业务逻辑计算的时候,就一定要调用 remove() 方法。这样可以避免内存泄漏和线程池中的问题。
本文详细解析了JavaThreadLocal的工作原理,包括数据存放和获取流程,内存泄露问题以及在线程池中的注意事项。特别强调了在使用后调用remove()方法以防止内存泄漏和线程池问题。
10万+

被折叠的 条评论
为什么被折叠?



