- ThreadLocal 是用于保存当前线程内的共享变量,其他线程访问不到,其核心数据结构是 ThreadLocalMap ,每个 Thread 会持有一个自己的 ThreadLocalMap
1、核心方法
(1)set
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程的 ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
// 如果 ThreadLocalMap 不为空,将当前 ThreadLocal 对象作为 key,和 value 放入 ThreadLocalMap
map.set(this, value);
else
// 如果 ThreadLocalMap 为空,创建一个 ThreadLocalMap ,放入数据,并赋值给当前 Thread
createMap(t, value);
}
(2)get
public T get() {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程的 ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
// 从 ThreadLocalMap 中获取当前 ThreadLocal 的 Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
// 如果 Entry 存在,返回具体的值
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 如果 ThreadLocalMap 是空的,初始化一个 ThreadLocalMap ,value 为 null 放进去,并返回
// 其实具体的逻辑等价于 set(null) 并返回 null
return setInitialValue();
}
(3)remove
public void remove() {
// 获取当前线程的 ThreadLocalMap
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
// 如果 ThreadLocalMap 不为空,移除当前 ThreadLocal
m.remove(this);
}
2、ThreadLocalMap
- ThreadLocalMap 其结构类似于 HashMap,维护了一个 Entry 数组,Entry 对象包括一个 key 弱引用的 ThreadLocal 对象和 value 具体的值
(1)构造方法
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
// 创建一个默认长度(16)的数据
table = new Entry[INITIAL_CAPACITY];
// 计算当前 key 的下标
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
// 创建一个 Entry 对象放入对应下标处
table[i] = new Entry(firstKey, firstValue);
// 设置长度为1
size = 1;
// 设置扩容阈值(数组长度 * 2 / 3)
setThreshold(INITIAL_CAPACITY);
}
(2)set
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
// 获取 Entry 数组
Entry[] tab = table;
// 获取 Entry 数组的长度
int len = tab.length;
// 计算当前 key 的下标
int i = key.threadLocalHashCode & (len-1);
// 循环处理下标元素不为空的情况(可能是 hash 冲突或者之前被添加过)
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
// 获取 key 的 ThreadLocal
ThreadLocal<?> k = e.get();
if (k == key) {
// 如果两个 key 相同,更新 value 后返回
e.value = value;
return;
}
if (k == null) {
// 如果 key 是空的,清空 Entry 再赋值后返回
replaceStaleEntry(key, value, i);
return;
}
}
// 下标处元素为空直接赋值
tab[i] = new Entry(key, value);
// 处理当前元素个数
int sz = ++size;
// 清空 key = null 的数据,并且判断是否需要扩容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
// 扩容
rehash();
}
(3)getEntry
private Entry getEntry(ThreadLocal<?> key) {
// 计算当前 key 的下标
int i = key.threadLocalHashCode & (table.length - 1);
// 获取下标处 Entry
Entry e = table[i];
if (e != null && e.get() == key)
// 如果 Entry 不为空并且 key 相同,返回 Entry
return e;
else
// 找到 key 相同的 Entry 返回,没有直接返回 null ,并处理 key = null 的情况
return getEntryAfterMiss(key, i, e);
}
(4)remove
private void remove(ThreadLocal<?> key) {
// 获取 Entry 数组
Entry[] tab = table;
// 获取 Entry 数组长度
int len = tab.length;
// 计算 key 的下标
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
// 找到 key 相同的 Entry
if (e.get() == key) {
// 清空 Entry 的 key 设置为 null
e.clear();
// 清理 key 为 null 的 Entry
expungeStaleEntry(i);
return;
}
}
}
3、InheritableThreadLocal
- InheritableThreadLocal 主要是为了解决子线程想获取父线程 ThreadLocal 的情况,其核心原理是在子线程创建的时候对父线程的 InheritableThreadLocal 做一个浅拷贝,只是重新创建了 ThreadLocalMap 和 Entry 数组,Entry 数组中的 key 和 value 并没有重新创建对象
(1)继承 InheritableThreadLocal
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
// 根据父线程的 inheritableThreadLocals 创建一个新的 ThreadLocalMap 赋值给自己的 inheritableThreadLocals
this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
// 最终会调用 ThreadLocalMap 的构造方法创建
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
(2)childValue
// 直接返回传过来的 value 没做任何处理
protected T childValue(T parentValue) {
return parentValue;
}
(3)getMap
// 返回该线程的 inheritableThreadLocals
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
(4)createMap
// 创建 ThreadLocalMap 并赋值给当前线程的 inheritableThreadLocals
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
4、内存泄漏问题
-
引用链
- ThreadLocal 引用 -> ThreadLocal 对象
- Thread 引用 -> Thread 对象 -> ThreadLocalMap 对象 -> Entry 数组 -> Entry -> Entry 弱引用 ThreadLocal 对象、强引用 value 对象
-
主动 remove 的情况
Thread 引用存在 | Thread 引用不存在 | |
---|---|---|
ThreadLocal 引用存在 | ThreadLocal 对象不会回收 value 对象会在下一次 gc 时回收 | ThreadLocal 对象不会回收 value 对象会在下一次 gc 时回收 |
ThreadLocal 引用不存在 | ThreadLocal 对象会在下一次 gc 时回收 value 对象会在下一次 gc 时回收 | ThreadLocal 对象会在下一次 gc 时回收 value 对象会在下一次 gc 时回收 |
- 不主动 remove 的情况
Thread 引用存在 | Thread 引用不存在 | |
---|---|---|
ThreadLocal 引用存在 | ThreadLocal 对象不会回收 value 对象不会回收 | ThreadLocal 对象不会回收 value 对象会在下一次 gc 时回收 |
ThreadLocal 引用不存在 | ThreadLocal 对象会在下一次 gc 时回收 value 对象会在下一次操作 ThreadLocalMap 时断开引用,断开引用后下一次 gc 时回收 | ThreadLocal 对象会在下一次 gc 时回收 value 对象会在下一次 gc 时回收 |
- 在日常开发中,请求都是由线程池去处理,所以 Thread 引用绝大多数情况是一直都存在的,就算 ThreadLocal 引用不存在,如果 Thread 没有再操作过 ThreadLocalMap,value 对象是不会被回收的,况且还有通过类的静态变量引用 ThreadLocal 对象导致 ThreadLocal 引用一直存在的情况