ThreadLocal详解
ThreadLocal是一个本地线程副本变量工具类,主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰。
说人话就是,ThreadLocal在每个线程都创建副本,每个线程可以访问自己的副本,线程之间相互不影响。
主要方法
void set(T value)
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//获取ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
//不为空设置
map.set(this, value);
else
//为空创建ThreadLocalMap
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
//返回线程Thread对象中维护的ThreadLocalMap变量
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
//创建一个新的ThreadLocalMap,并绑定到t中
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
T get()
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程维护的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
//获取ThreadLocalMap.Entry对象
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
//不为空,直接返回
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//为空,返回默认值
return setInitialValue();
}
private Entry getEntry(ThreadLocal<?> key) {
//获取当前key对应的索引
//这个方法比较有趣,详细请看[threadlocal中hash](https://blog.csdn.net/y4x5M0nivSrJaY3X92c/article/details/81124944)
int i = key.threadLocalHashCode & (table.length - 1);
//获取Entry对象,实际存储信息的对象
Entry e = table[i];
if (e != null && e.get() == key)
//hash不冲突,并且不为空,返回对象
return e;
else
//hash冲突,或者e对象为空的情况下
return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
//不空,即代表hash冲突
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e;
//如果key为空,清除,避免内存泄漏
if (k == null)
expungeStaleEntry(i);
else
//向后查找一个1,即i = i+1
i = nextIndex(i, len);
e = tab[i];
}
//e为空的情况下返回空
return null;
}
private T setInitialValue() {
//初始化null
T value = initialValue();
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程维护的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
//设置对象
map.set(this, value);
else
//新建一个map
createMap(t, value);
return value;
}
//初始化一个空对象
protected T initialValue() {
return null;
}
void remove()
public void remove() {
//获取当前线程的ThreadLocalMap
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
//不为空的话,直接清除当前对象的Entry
m.remove(this);
}
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)]) {
//遍历查找相同的key,有的话清楚
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
内存泄漏
什么叫内存泄漏?
内存泄露为程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
广义并通俗的说,就是:不再会被使用的对象或者变量占用的内存不能被回收,就是内存泄露。
ThreadLocal的代码结构,如图所示:
由此,我们可以知道,一般的调用链为:
ThreadPool -> Thread -> ThreaLocalMap -> Entry -> value
如果此时,ThreadLocal使用完之后,讲栈中它的变量置为null,ThreadLocal 对象下一次 GC 会被回收,那么 Entry 中的与之关联的弱引用 key 就会变成 null。如果此时当前线程还在运行(线程池情况),那么 Entry 中的 key 为 null 的 Value 对象并不会被回收(存在强引用);如果当前线程执行完毕会被回收(ThreaLocalMap的生命周期和Thread生命周期一起完结),那么 Value 自然也会被回收。
为什么Value存在强引用?
可以通过数据结构看出来,v变量被指向value变量,这个就是强引用
static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
以上,就是ThreadLocal内存泄漏的来源。那么,如何避免?
ThreadLocal 为了降低内存泄露的可能性,在set、get、remove操作时,会清除线程ThreadLocalMap中Entry所有数组中key为null的value值。
所以,当前线程使用完 threadlocal 后,我们可以通过调用 ThreadLocal 的 remove 方法进行清除从而降低内存泄漏的风险。