使用
ThreadLocal<String> threadLocalA= new ThreadLocal<String>();
threadLocalA.set(new String("A"));
String str = threadLocalA.get();
原理
在每个线程的内部有个数据结构为Map的threadLocals属性,以<ThreadLocal,Value>的形式保存着线程变量和其对应的值。
当使用get()方法时,分为两步:
- 获取到当前线程的threadLocals,类型为Map。
- 以ThreadLocal为Map的key获取到它的value值。
源码
get()方法:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);//当前线程为入参,获取当前线程的threadLocals
if (map != null) {
//入参为this,也就是说key为ThreadLocal
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;//threadLocals为线程的属性
}
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
return getEntryAfterMiss(key, i, e);//避免内存泄漏,下文有提。
}
内存泄漏
ThreadLocalMap结构如下:
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k); //key,这个是一个弱引用,如果没有强引用来引用key(也就是ThreadLocal),则key会被回收,形成了key为null的Entry。
value = v;
}
}
private Entry[] table;
}
ThreadLocalMap是在线程内部的,故只要线程被回收了就不会存在内存泄漏。
ThreadLocal被回收时(key为null),没有办法获取到value,而线程又不会被回收时则value一直占用空间导致内存泄漏。线程不会被回收的常见场景是线程池。
JDK在此做了一个优化,在调用get(),set(),remove()方法会做额外处理来清理ThreadLocalMap中key为null的value,以减少内存泄漏的影响。但是如果key未使用弱引用,即时ThreadLocal被回收了,key也不为null,也就是说是没法判断哪个value需要回收的,最终造成内存泄漏。所以此处的弱引用key是内存泄漏的一个优化处理方式。