简介
ThreadLocal和Synchronized一样,都是用于处理线程间变量问题;后者有用于等待方式处理变量,前者用多个副本处理对象,时间和空间牺牲;那么ThreadLocal内部是如何用副本的形式管理的呢?继续往下看
ThreadLocal一般使用
ThreadLocal<String> t = new ThreadLocal<>();
t.set("name");
一般就是上面的用法,我们看看代码内部是如何实现的?
public ThreadLocal() {
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
set设置值时,会getMap()获取一个ThreadLocalMap,如果这个map是一个null的话,就需要创建一个ThreadLocalMap,那么他是如创建的呢?
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
在createMap中就是new了一个ThreadLocalMap,但是要注意他把这个map赋值给了t,也就是Thread,所以如果是同一个线程里面有多个ThreadLocal对象,其实是用同一个ThreadLocalMap
ThreadLocalMap内部结构,是一个Entry数组,通过ThreadLocal的hashcode确定在数组中的位置,然后key是ThreadLocal,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的key实质是一个弱引用,也就是Map持有的ThreadLocal是一个弱引用,map的持有并不影响对它的GC,但是value会影响,map被Thread持有,而Thread几乎可以作为GC Root的根节点(根搜索法),很有可能造成内存泄漏
比如这种情况就会有内存泄漏,当前Thread线程存活时间很久,并且有多个ThreadLocal成员,如果其中一个ThreadLocal不用了,那么这个local的value由于Thread存活很久而造成内存泄漏
官方也意识到了这个问题,修改了部分源码,在对ThreadLocal调用get时候会自动清楚掉Entry数组中key为null的value,这样就不会造成内存泄漏了;
但是还是可能会出现,一种就是粗心的程序员没有去get;另一种是把ThreadLocal设置为static属性
总结
综上所述,同一个线程中的多个ThreadLocal共用一个Entry的Table;不同线程有自己独立的table;为了不造成内存泄漏,使用完后get或者remove掉,以防出现内存泄漏