ThreadLocal简介
ThreadLocal一般用于存储线程级别变量,存储的变量是绝对的线程安全。用的比较多的场景就是存储线程级别变量来传递某些参数,并且注意的是不能滥用。某些情况下不适合用方法参数时,才考虑使用ThreadLocal,并且需要清楚ThreadLocal何时设置值,何时回收。
如何保证线程安全?
java.lang.ThreadLocal#set
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
上面是ThreadLocal中set方法,可以看到是先获取当前线程的对象Thread,然后获取Thread中的一个ThreadLocalMap,并且ThreadLocal作为key,把val存储到map中。设置值的过程不存在线程安全问题,因为此时不会有别的线程对这个线程ThreadLocalMap进行读取或者写入。
##java.lang.ThreadLocal#get
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
上面是ThreadLocal的get方法,读取值的时候也是,先获取到当前的线程对象,然后从对应线程对象中的Map中读取相应的值。同样,只会有当前线程在操作这个map,所以是绝对的线程安全。
#ThreadLocal的问题
##如何回收ThreadLocal?
当某个ThreadLocal不再被引用,但是线程还存在时,在ThreadLocalMap中的如何回收这个ThreadLocal?答案是依靠弱引用机制。
java.lang.ThreadLocal.ThreadLocalMap.Entry代码
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
//k就是弱引用指向
super(k);
value = v;
}
}
上面是ThreadLocalMap内部存储元素,当新设置一个ThreadLocal值,就新建一个Entry对象,Entry对象继承WeakReference,这样当ThreadLocal
不再被引用时,因为弱引用机制原因,当jvm发现内存不足时,会自动回收弱引用指向的实例内存,即其线程内部的ThreadLocalMap
会释放其对ThreadLocal
的引用从而让jvm回收ThreadLocal
对象。
##如何回收value?
但是这里需要注意的是value值v并不是弱引用指向所以不会被回收,所以若果设置的value值占用内存比较大的话,容易出现内存泄漏。但是其实在jdk中已经有相应的解决办法。
java.lang.ThreadLocal.ThreadLocalMap#getEntry
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
//查找出无效的entry并且删除
return getEntryAfterMiss(key, i, e);
}
java.lang.ThreadLocal.ThreadLocalMap#getEntryAfterMiss
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e;
if (k == null)
//ThreadLocal已经null,就要释放value,这里设置Entry.value=null
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
这里设置Entry.value=null,没了引用链,gc时就能回收value的值。在Map进行set的时候也可以看到类似的代码。
但是我们可以知道只在 e != null && e.get() == key时才会触发。
所以一般都需要手动调用ThreadLocal的remove,以此避免内存泄漏。
线程池使用ThreadLocal问题
如果使用线程池使用ThreadLocal,需要注意,每次使用前,需要把线程中之前存在的ThreadLocalMap中的值清除掉,否则很可能出现业务上的错误。