ThreadLoacl 源码分析
ThreadLoacl#Set方法
ThreadLocal 类中有一个属性是 ThreadLocal.ThreadLocalMap threadLocals
下面的源码关键点就是 ThreadLocalMap 的 set 方法
public void set(T value) {
// 拿到当前线程
Thread t = Thread.currentThread();
// 拿到当前线程中的 ThreadLocalMap 对象
ThreadLocalMap map = getMap(t);
if (map != null) {
// 如果不是空,往ThreadLocalMap set value
map.set(this, value);
} else {
// 如果是空的,初始化一个 ThreadLocalMap ,然后把 set value
createMap(t, value);
}
}
ThreadLocalMap#set方法
Entry 的 引用是空的话,也直接覆盖值解释:Entry 的 key 是 threadLocal,但是是一个弱饮用,如果这个threadLocal被回收的话,那么就直接覆盖,后面内存泄露部分会说,为什么是弱引用而不是强引用
整体流程就是,通过 key.threadLocalHashCode & (len-1); 找到一个初始位置
如果位置已经有元素了并且不是自己(哈希冲突),就是就会循环一直往后找(解决哈希冲突的方式),直到找到位置。
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.
// ThreadLocalMap 内存存储就是一个 Entry 数据组
Entry[] tab = table;
int len = tab.length;
// key 是当前 ThreadLocal,threadLocalHashCode是 ThreadLocal 的一个属性
// 感兴趣的可以去看看 threadLocalHashCode 的生产规则
// 来确定 value 的初始扫描位置
int i = key.threadLocalHashCode & (len-1);
// for 循环来最终确定 value 的位置,结束循环条件是 e == null
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
// 如果 Entry 的 引用是当前 threadlocal 的话,就是直接覆盖值了
if (e.refersTo(key)) {
e.value = value;
return;
}
// 如果 Entry 的 引用是空的话,也直接覆盖值
if (e.refersTo(null)) {
replaceStaleEntry(key, value, i);
return;
}
}
// 到这里说明 e == null(此哈希槽没有被占用,可以直接用)
tab[i] = new Entry(key, value);
int sz = ++size;
// cleanSomeSlots 是清理 Entry ,如果Entry的key为空的话,就会直接回收
// 整体就是 如果没有清理到东西,并且占用的 hash槽的占用已经超过了阈值,默认 threshold = len * 2 / 3; 那么就可能触发rehash
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
ThreadLocalMap#rehash And ThreadLocalMap#expungeStaleEntries
private void rehash() {
//
expungeStaleEntries();
// Use lower threshold for doubling to avoid hysteresis
// 若在 expungeStaleEntries() 清除过后 size>=len/2(也就是 threshold-threshold/4)则调用 resize() 扩容。
// 感兴趣的可以看看 resize(), 扩容大小是原来的两倍
if (size >= threshold - threshold / 4)
resize();
}
private void expungeStaleEntries() {
Entry[] tab = table;
int len = tab.length;
// 循环整个 Entry[],如果key 为空,那么就直接清理掉
for (int j = 0; j < len; j++) {
Entry e = tab[j];
if (e != null && e.refersTo(null))
expungeStaleEntry(j);
}
}
threadlocal操作内存泄漏的原因
ThreadLocal对象就相当于钥匙,Thread 对象需要用钥匙去自己内部 ThreadLocalMap 去拿到值。
ThreadLocalMap是一个哈希数组,key为ThreadLocal对象,Value为一个Object类型的值
这就意味着可以创建多个ThreadLocal来获取不同ThreadLocal往这个Map里存的值
弱引用-解决内存泄漏
ThreadLocal对象就相当于钥匙,如果线程一直没有死,但是 ThreadLocal 对象已经没有地方可以用了(一般用作局部变量的时候,方法执行完,就无法再使用了),如下
public void test(){
ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("test");
}
但是 ThreadLocal 的 ThreadLocalMap 还有对它的引用,所以一直无法回收。
所以后面官方就把 ThreadLocalMap 的 key 引用从强引用换成了弱饮用,如果没有其他强引用去引用 ThreadLocal ,在GC 的时候就会被回收,代码中也会把 ThreadLocalMap 中的 Entry 的 key 为 null 的 Entry给清理掉
可能发生内存泄露的情况
但是一般 ThreadLocal 用作类变量,如下
public final static ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<>();
有强引用就不会在GC的时候被回收,但是如果线程一直没有死的话(例如线程池),那么 ThreadLocalMap 就会一直壮大。
但是实际上 ThreadLocal 对象一般是有限的,例如三个的话,那么一个线程的 ThreadLocalMap 也只会有 3 个 k(localhost) v(业务对象)
get 方法
跟set方法差不多,就不做概述了