1、ThreadLocal定义
ThreadLocal,即线程变量,是一个以ThreadLocal对象为键、任意对象为值的存储结构。这个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值。
同一个ThreadLocal所包含的对象,在不同的Thread中有不同的副本。有以下两点需要注意:
●因为每个Thread内有自己的实例副本,且该副本只能由当前Thread使用。这也是ThreadLocal命名的由来。
●既然每个Thread有自己的实例副本,且其他Thread不可访问,那就不存在多线程间共享的问题。
2、实现原理
set()方法
每一个线程都拥有一个ThreadLocalhostMap,如果没有,在第一次执行set()方法时创建一个ThreadLocalhostMap,也就是下面的createMap()方法,只需new一个ThreadLocalMap传给Thread的成员变量threadLocals;如果存在一个ThreadLocalhostMap,获得该Thread的threadLocals,并插入新的键值对(threadLocal和要set的value);
public void set(T value) {
//找到当前线程
Thread t = Thread.currentThread();
//获取当前线程的ThreadLocalMap
ThreadLocalMap map = getMap(t);
//如果map不为空,就插入这组键值对
if (map != null)
map.set(this, value);
//如果map为空,就创建一个map
else
createMap(t, value);
}
//获得线程的ThreadLocalMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//Thread类持有ThreadLocalMap类型的成员变量threadLocals
ThreadLocal.ThreadLocalMap threadLocals = null;
//创建ThreadLocalMap
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap的结构
每个线程有自己的ThreadLocalMap,ThreadLocalMap的元素是Entry,是ThreadLocal和要set的value。ThreadLocalMap和HashMap有点类似,不过HashMap是数组+链表的数据结构,采用拉链法解决hash冲突;而ThreadLocalMap是一个Entry数组,采用开放定址法解决hash冲突。
ThreadLocalMap的源码
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);
}
get()方法
是根据ThreadLocal获取当前线程ThreadLocalMap下的Entry,进而获取Entry中的value值。
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
//获取当前ThreadLocal对于的Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
//获取value
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
3、Entry中的key为什么是弱引用的
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是弱引用的,那为什么不是强引用呢,如果是强引用,在线程里把ThreadLocal置为null,那就意味着从线程栈指向堆中的ThreadLocal实例的强引用将不存在,这样堆里的ThreadLocal实例就会被回收了吗,答案是否定的,因为它还有一条key对ThreadLocal的强引用,ThreadLocal将不会被回收,会导致内存泄露。如果是弱引用的话,ThreadLocal会被回收, ThreadLocalMap中使用这个 ThreadLocal 的 key 也会被清理掉。
4、内存泄露
虽然Entry中的key为弱引用,但ThreadLocal仍会导致内存泄露,原因是value仍存在ThreadLocalMap对它的强引用,不会被回收。只有当前thread结束以后, current thread就不会存在栈中,强引用断开, Current Thread, Map, value将全部被GC回收。当线程的某个ThreadLocal使用完了的时候及时调用ThreadLocal的remove方法,防止内存泄露。