ThreadLocal允许将每个线程与持有数值的对象关联起来。
ThreadLocal提供了get和set访问器,为每个使用它的线程维护一份单独的拷贝,所以get总是返回当前执行线程通过set设置的最新的值。使用ThreadLocal 变量来实现线程隔离。
ThreadLocal持有事务的上下文,当框架代码需要获得当前正在运行的哪个事务时,只需要从ThreadLocal中get事务的上下文即可,避免了多线程竞争导致的锁等消耗。
//将数据库链接的key放到ThreadLocal本地线程中,那么每个线程就会拥有属于自己的DataSource connection
private static final ThreadLocal<DBTypeEnum> contextHolder = new ThreadLocal<>();
public static void set(DBTypeEnum dbType){
contextHolder.set(dbType);
}
public static DBTypeEnum get(){
return contextHolder.get();
}
public static void remove(){
contextHolder.remove();
}
1、ThreadLocal实现原理
为每个Thread 维护一个 ThreadLocalMap 映射表,这个映射表的 key 是 ThreadLocal实例本身,value 是真正需要存储的 Object。
//当前线程
Thread t = Thread.currentThread();
//维护的ThreadLocalMap映射表
ThreadLocalMap map = getMap(t);
//ThreadLocalMap映射表: key 是 ThreadLocal实例本身;value 是真正需要存储的 Object
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
2、ThreadLocal 内存泄漏的原因
从上面的源代码可以看到,threadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal不存在外部强引用时,Key(ThreadLocal)势必会被GC回收,这样就会导致ThreadLocalMap中key为null, 而value还存在着强引用,只有thead线程退出以后,value的强引用链条才会断掉。
但如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:
Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value
导致value永远无法回收,造成内存泄漏。
3、如何解决ThreadLocal内存泄漏?
每次使用完ThreadLocal都调用它的remove()方法清除数据。
void demoTest {
threadlocal.set(xxx);
try {
// do something
} finally {
threadlocal.remove();
}
}
将ThreadLocal变量定义成private static,这样就一直存在ThreadLocal的强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉 。
threadLocal的remove()方法源码:
private void remove(ThreadLocal<?> key) {
//使用hash方式,计算当前ThreadLocal变量所在table数组位置
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
//再次循环判断是否在为ThreadLocal变量所在table数组位置
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
//调用WeakReference的clear方法清除对ThreadLocal的弱引用
e.clear();
//清理key为null的元素
expungeStaleEntry(i);
return;
}
}
}
//清理key为null的元素
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// 根据强引用的取消强引用关联规则,将value显式地设置成null,去除引用
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// 重新hash,并对table中key为null进行处理
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
//对table中key为null进行处理,将value设置为null,清除value的引用
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
4、这里ThreadLocalMap的key为什么使用弱引用而不是强引用?
-
当threadLocalMap的key为强引用回收ThreadLocal时,因为ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。
-
当ThreadLocalMap的key为弱引用回收ThreadLocal时,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。当key为null,在下一次ThreadLocalMap调用set(),get()时候,其remove()方法的时候会被清除value值。
本文详细探讨了ThreadLocal的工作原理,解释了内存泄漏的原因,并提供了清除ThreadLocal内存泄漏的方法。重点介绍了为何使用弱引用作为ThreadLocalMap的key,以及如何避免和解决内存泄漏问题。

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



