ThreadLocal源码解析
Posted miaomiaoLoveCode
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ThreadLocal源码解析相关的知识,希望对你有一定的参考价值。
引言
ThreadLocal,线程变量,线程可以将本次线程内经常使用的变量存储到ThreadLocal中,方便本次线程内其他的操作使用。
注:特别需要注意的是,有些博客说ThreadLocal可以保证线程安全,这是错误的认识,ThreadLocal存储的只是每一个线程的本地变量,并未涉及到临界区,不能保证线程安全。在使用的时候一定要注意使用场景,ThreadLocal存储的应该是每个线程内部共享的一些数据,而非临界区数据。
ThreadLocal源码解析
用过ThreadLocal的程序员们可能都知道,ThreadLocal最常用的三个方法无非就是get(),set(T value)和remove(),本文就这三个常用的方法来分析ThreadLocal的相关源码。
- ThreadLocalMap
在分析具体操作源码之前,先看看ThreadLocal底层的存储结构ThreadLocalLocalMap吧,这里着重讲解它的get/set/remove操作,关于其他部分的源码,有兴趣的读者可以自行阅读。
get操作
private Entry getEntry(ThreadLocal key)
//找到key在hashEntry中的位置
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
//找到的值是当前查询的key的值,返回
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
从上面的源码可以看出,假如直接hash找到了对应的slot,返回即可,假若没找到呢。。。没找到执行下面的操作:
private Entry getEntryAfterMiss(ThreadLocal key, int i, Entry e)
Entry[] tab = table;
int len = tab.length;
//在table里查询,如若找到待查询entry,返回该entry
while (e != null)
ThreadLocal k = e.get();
//entry的key为待查询key
if (k == key)
return e;
//entry的key为null时,表明当前的entry并不是最新的,需要做相关的操作删除掉非最新的entry
if (k == null)
expungeStaleEntry(i);
else
//i++,i == len 时 i = 0
i = nextIndex(i, len);
e = tab[i];
return null;
set操作
//set操作,key为当前ThreadLocal对象,value为对应的变量值
private void set(ThreadLocal key, Object value)
Entry[] tab = table;
int len = tab.length;
//hash出当前key所在slot
int i = key.threadLocalHashCode & (len-1);
//处理tab[i] != null
for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)])
ThreadLocal k = e.get();
//key存在,覆盖value
if (k == key)
e.value = value;
return;
//key不存在,需要在table对应位置插入entry
if (k == null)
replaceStaleEntry(key, value, i);
return;
//tab[i] == null,直接做插入
tab[i] = new Entry(key, value);
int sz = ++size;
//sz >= len * loadFactor(loadFactor = 2/3)需要resize
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
remove操作
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)])
//找到对应的entry
if (e.get() == key)
//clear entry
e.clear();
//删掉该entry
expungeStaleEntry(i);
return;
ThreadLocalMap的相关重要操作到这里就分析完毕,ThreadLocal的相关操作都是在ThreadLocalMap操作基础上封装的。
- ThreadLocal.get()
public T get()
Thread t = Thread.currentThread();
//获取当前线程的threadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
//entry不为null,返回对应的value
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
//否则,返回null
return setInitialValue();
- ThreadLocal.set(T value)
public void set(T value)
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
//当前线程的threadLocalMap已创建,直接往map中set值
if (map != null)
map.set(this, value);
//当前线程的threadLocalMap未创建,需要先创建再set
else
createMap(t, value);
- ThreadLocal.remove()
public void remove()
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
后记
到这里为止,ThreadLocal的相关源码分析就结束了。在最后还需要讲讲之前我在使用ThreadLocal遇到的坑。
在业务线工作的时候,发现很多时候都有线程内共享变量的需求,ThreadLocal最适合这个场景,但是在使用ThreadLocal的时候,总是忘记remove,终于出现了一次巨大的故障。。。ThreadLocal和ThreadPoolExcutor一起使用时,出现了内存泄漏,在那一刹那,机器内存使用率蹭蹭往上涨,排查了很久,才发现是ThreadLocal忘记remove了,为什么ThreadLocal不remove会造成内存泄漏呢?
ThreadLocal导致的内存泄漏
之前查过很多资料,都是ThreadLocal不remove不会导致内存泄漏,但是事实却不是如此,血一般的教训啊。
每个Thread都有一个ThreadLocalMap,虽说它是弱引用,但是弱引用仅仅都是针对key,每个key都弱引用指向ThreadLocal。当把ThreadLocal实例置为null的时候,没有任何的强引用指向ThreadLocal实例,ThreadLocal将会被gc回收,但是,value却不能被回收,因为存在一条从当前线程连接过来的强引用,只有当前
thread结束以后,thread,map,value将会全部被gc回收。
针对这个问题,ThreadLocal为了减少内存泄漏的机会,在get/set的时候都会检查ThreadLocalMap中是否有key == null的value,会把这部分value删除掉。程序员们可能这时候有这个疑惑,既然都这么做来避免了,为什么会出现内存泄漏啊?但是,大家就没想过么,假若线程不被销毁,它的ThreadLocal的get/set也一直未被使用,这不就出现了实际意义上的内存泄漏么?刚好线程池里的一部分核心线程是不会被销毁的,假若出现这种情况是一定会出现内存泄漏的!!!所以,在使用ThreadLocal结束之后一定不要忘记remove!!!
以上是关于ThreadLocal源码解析的主要内容,如果未能解决你的问题,请参考以下文章