源码分析:ThreadLocal
关键方法
set()方法
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取线程中的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
// 判断ThreadLocalMap对象是否为null,为null则去创建ThreadLocalMap对象
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
set的方法的源码比较简单易读,里面的关键部分就是去获取线程的ThreadLocalMap对象。ThreadLocalMap存在,就直接将值set到ThreadLocalMap中,并且将ThreadLocal对象做为key值。反之会去调用createMap方法创建ThreadLocalMap。
createMap()
/**
* Create the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the map
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
这个方法就比较简单,在ThreadLocal中,定义了一个ThreadLocalMap的静态类,这里直接创建这个静态类的对象,并且将线程中的ThreadLocalMap成员变量的引用指向它。
getMap()
/**
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
getMap就更加显而易见啦,就是直接返回线程中的ThreadLocalMap对象咯。
map.set()
/**
* Set the value associated with key.
*
* @param key the thread local object
* @param value the value to be set
*/
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
// 获取ThreadLocalMap内部的数组,ThreadLocalMap基于数组实现的
Entry[] tab = table;
// 获取数组长度
int len = tab.length;
// 根据key值的hashcode,计算index值
int i = key.threadLocalHashCode & (len-1);
// 这里通过循环的方式去获取数组上面的元素
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
// 获取key值,这个key值,就是threadlocal对象咯
ThreadLocal<?> k = e.get();
// 如果key值相等,直接替换对象e里面的value值
if (k == key) {
e.value = value;
return;
}
// 如果key为null,但是这个位置存在一个entry对象,则调用replaceStaleEntry函数去处理
if (k == null) {
// 这个函数在下面有详细的步骤分解
replaceStaleEntry(key, value, i);
return;
}
}
// 当key值不存在ThreadLocalMap里面,就创建一个Entry对象,并放入到数组中
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
// rehash方法的作用是去清空数组中,key值为空的entry对象
rehash();
}
/**
* Replace a stale entry encountered during a set operation
* with an entry for the specified key. The value passed in
* the value parameter is stored in the entry, whether or not
* an entry already exists for the specified key.
*
* As a side effect, this method expunges all stale entries in the
* "run" containing the stale entry. (A run is a sequence of entries
* between two null slots.)
*
* @param key the key
* @param value the value to be associated with key
* @param staleSlot index of the first stale entry encountered while
* searching for key.
*/
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
// Back up to check for prior stale entry in current run.
// We clean out whole runs at a time to avoid continual
// incremental rehashing due to garbage collector freeing
// up refs in bunches (i.e., whenever the collector runs).
// staleSlot是当前新计算出来的,需要插入数据的数组位置
int slotToExpunge = staleSlot;
// 这里的for循环,是根据当前的staleSlot值,向前去寻找key值为null的entry对象,并且用slotToExpunge记录数组的下标位置
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len))
if (e.get() == null)
slotToExpunge = i;
// Find either the key or trailing null slot of run, whichever
// occurs first
// 这里的for循环,是根据当前staleSlot值,向后去寻找数组中的entry对象
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
// If we find key, then we need to swap it
// with the stale entry to maintain hash table order.
// The newly stale slot, or any other stale slot
// encountered above it, can then be sent to expungeStaleEntry
// to remove or rehash all of the other entries in run.
// 如果entry对象的key值不为null
if (k == key) {
// 将entry对象的值替换成新值
e.value = value;
// 这个数组位置给置为空
tab[i] = tab[staleSlot];
// 将entry值放入到数组的新的位置中
tab[staleSlot] = e;
// Start expunge at preceding stale entry if it exists
if (slotToExpunge == staleSlot)
slotToExpunge = i;
//这个函数是用于清空数组中key值为null的entry对象的,防止这些对象造成内存泄露
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
// If we didn't find stale entry on backward scan, the
// first stale entry seen while scanning for key is the
// first still present in the run.
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
// If key not found, put new entry in stale slot
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
// If there are any other stale entries in run, expunge them
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
关键点来啦,在ThreadLocalMap内部,是维护了一个数组,而数组中,放的是一个Entry对象,下面接着看Entry对象的定义。
/**
* 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.
*/
// 这里的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类继承了弱引用,在构造方法中,ThreadLocal对象在这里,是用弱引用指向的,所在在ThreadLocalMap中,key值是存的是ThreadLocal对象的弱引用,而不是强引用哦,这里ThreadLocal就巧妙的使用了弱引用去解决了Map中key值内存泄露问题。(若知后续如何,可以直接拖到内存泄露剖析)
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.
*
* @return the current thread's value of this thread-local
*/
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();
}
get方法的代码逻辑也不复杂,主要是得到线程map,调用map的getEntry()方法获取当前entry对象,然后从entry对象中取出value值。
remove()方法
/**
* Removes the current thread's value for this thread-local
* variable. If this thread-local variable is subsequently
* {@linkplain #get read} by the current thread, its value will be
* reinitialized by invoking its {@link #initialValue} method,
* unless its value is {@linkplain #set set} by the current thread
* in the interim. This may result in multiple invocations of the
* {@code initialValue} method in the current thread.
*
* @since 1.5
*/
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
/**
* Remove the entry for key.
*/
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
// 计算key在数组中的index值
int i = key.threadLocalHashCode & (len-1);
// 这里通过循环调用expungeStaleEntry函数,去清理数组中的entry
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
// 这个函数,是用于清理数据组的entry对象的。具体源码的话,这里就不详细说明了
expungeStaleEntry(i);
return;
}
}
}
内存泄露剖析
在前面提到了,ThreadLocal使用了弱引用的方式来解决key值内存泄露的问题,在这里,就简单的说一下我对ThreadLocalkey泄露的问题的理解。
如上图所示,展示了ThreadLocal在内存中的引用关系图。图中实线表示强引用,虚线表示弱引用。
1、普通线程使用ThreadLocal
普通线程使用完ThreadLocal,断开ThreadLocal的强引用(设为null),当线程销毁后,那么指向ThreadLocalMap的那条强引用线就会断掉,这个时候ThreadLocalMap就没有强引用指向它,此时就算ThreadLocalMap中的key值是强引用指向ThreadLocal,也会被垃圾回收器回收掉(因为此时,已经无没有GCROOT对象指向他们了),这种情况,不会出现内存泄露
2、线程池使用ThreadLocal
线程池中的线程,使用ThreadLocal后,断开ThreadLocal的强引用(设为null),但是线程存在于线程池中,未被销毁。设想一下,把图中的弱引用换成强引用,如果此时ThreadLocalMap的key值是强引用指向ThreadLocal的,那么这个ThreadLocal,是不是就永远回收不掉呢。(弱引用:每一次GC时候,被若引用指向的对象,会被GC垃圾收集器回收掉)
3、value值内存泄露
通过上面的描述,可能就发现了问题,弱引用解决了key值内存泄露的问题,但是value值也存在内存泄露的可能性呀。 ThreadLocalMap中的value值,是强引用指向的它的,所以当key值被GC掉后,那么value值在一直存在于map中,并且还取不出来。这会儿,就需要我们在使用的过程中,使用完后,主动调用remove()方法,去清理掉ThreadLocalMap中的值,remove方法不仅仅可以清理掉value值,同时也会清理ThreadLocalMap中,所有无效的值。
总结
1、ThreadLocal主要用于保存当前线程的本地变量,可以起到变量之间的线程隔离,常用的场景就有数据库的会话连接、多数据源等。
1、在使用ThreadLocal的时候,注意在使用完后,主动remove(),避免内存泄露问题
2、在使用ThreadLocal的时候,使用static去标识, 原因是防止重复创建ThreadLocal对象,使用static标识后,静态变量只会创建一次。如果不使用static标识也可以,不会出现错误,但是会造成内存空间的浪费。
(以上是个人对ThreadLocal的源码剖析,还有理解,有差错的地方,欢迎指正)