ThreadLocal是什么?
ThreadLocal字面意思是本地线程,其实更准确来说是线程局部变量,线程类Thread有个变量叫做threadLocals,他的类型就是ThreadLocal.ThreadLocalMap类型,其实它就是一个map类型,key是当前线程的ThreadLocal对象,值就是你要保存的数据。
ThreadLocal有什么用?
我们知道,在多线程并发执行时,一方面,需要进行数据共享,于是才有了volatile变量解决多线程间的数据可见性,也有了锁的同步机制,使变量或代码块在某一时该,只能被一个线程访问,确保数据共享的正确性。其中,Synchronized用于线程间的数据共享的。另一方面,并不是所有数据都需要共享的,这些不需要共享的数据,让每个线程单独去维护就行了,ThreadLocal就是用于线程间的数据隔离的。
ThreadLocal类的源码
我们先来看看ThreadLocal类是如何为每个线程创建一个变量的副本的。
首先get方法的实现:
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*/
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")
}
}
return setInitialValue();
}
第一句是取得当前线程,然后通过getMap(t)方法获取到一个map,map的类型为ThreadLocal.ThreadLocalMap。
然后接着下面获取到Entry键值对,注意这里获取Entry时参数传进去的是this,即ThreadLocal实例,而不是当前线程t。如果获取成功,则返回value值。
如果map为空,则调用setInitialValue方法返回一个初始value,其实这个默认初始value为null。
接着来看一下getMap方法做了什么:
/* ThreadLocal values pertaining to this thread. This map is maintained by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/**
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
在getMap中,是调用当期线程t,返回当前线程t中的一个成员变量threadLocals,类型为ThreadLocal.ThreadLocalMap。就是上面提到的每一个线程都自带一个ThreadLocalMap成员变量。
继续来看ThreadLocalMap的实现:
static class ThreadLocalMap {
/**
* 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> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal> k, Object v) {
super(k);
value = v;
}
}
总结一下
在每个线程Thread内部有一个ThreadLocalMap类型的成员变量threadLocals,这个ThreadLocalMap成员变量的Entry的Key为,当前ThreadLocal变量的WeakReference封装,value为要存储的变量。
为何ThreadLocalMap的键值为ThreadLocal对象?因为每个线程中可能需要有多个threadLocal变量,也就是ThreadLocalMap里面可能会有多个Entry。
上面我们提到,每个线程第一次调用ThreadLocal.get方法时,内部会走到setInitialValue方法返回一个初始value,其实这个默认初始value为null,这里要注意的一个是,null赋给基本数据类型时会抛空指针。
ThreadLocal的内存泄露问题
我们知道一个线程使用完之后并不会销毁,而是会回到线程池进行复用,也就是说,你保存在当前线程中的变量实例还是绑定在线程上的。
由于WeakReference封装了ThreadLocal,并作为了ThreadLocalMap的Entry的Key。如果在某些时候ThreadLocal对象被赋Null的话,弱引用会被GC收集,这样就会导致Entry的Value对象找不到,线程被复用后如果有调用ThreadLocal.get方法的话,方法里面会去做遍历清除Key为null的Entry,但如果一直没调用ThreadLocal.get方法的话就会导致内存泄漏了。
所以一般线程用完ThreadLocal后,要调用threadLocal.remove()方法清除保存在当前线程中的数据变量。
说了这么多好像没讲到怎么用啊,想了解项目中怎么用的,这里这里
最后再补充一段很有意思的代码
就是我们上面提到的get()里map.getEntry(this)的逻辑,其实这里很有讲究,直接附上源码,其中注释都补上了,大家自行享用:
/**
* itself handles only the fast path: a direct hit of existing
* key. It otherwise relays to getEntryAfterMiss. This is
* designed to maximize performance for direct hits, in part
* by making this method readily inlinable.
*
* @param key the thread local object
* @return the entry associated with key, or null if no such
*/
private Entry getEntry(ThreadLocal> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
// 散列到的位置,刚好就是要查找的对象
// 直接散列到的位置没找到,那么顺着hash表递增(循环)地往下找
return getEntryAfterMiss(key, i, e);
}
getEntryAfterMiss()方法,这里就有我们上面内存泄漏问题提到的遍历清除Key为null的Entry逻辑
/**
* Version of getEntry method for use when key is not found in
* its direct hash slot.
*
* @param key the thread local object
* @param i the table index for key's hash code
*/
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)
// 如果Key为null,将所有value都设为null,jvm会自动回收掉无用的对象
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}