使用方法
ThreadLocal可以存储线程的本地属性,可以用来做线程相关的数据缓存以及保存traceId等。但是由于现在线程基本由线程池创建,所以使用ThreadLocal的时候会出现内容与预期不符的情况,这是因为线程池内的线程是复用的,解决方案是使用完毕就调用threadLocal.remove()方法
源码分析
ThreadLocal内有一个静态内部类ThreadLocalMap。实际上,ThreadLocalMap才是数据真正存储的地方,ThreadLocal仅仅是一个工具类,提供了可操作ThreadLocalMap的静态方法。
image.png
ThreadLocalMap
ThreadLocalMap 中有一个静态内部类Entry,弱引用了ThreadLocal,同时存储了Object类型的value值
弱引用: 当一个对象仅仅被weak reference(弱引用)指向, 而没有任何其他strong reference(强引用)指向的时候, 如果这时GC运行, 那么这个对象就会被回收,不论当前的内存空间是否足够,这个对象都会被回收。
ThreadLocalMap 内部维护了一个Entry数组table,数组的初始化大小为16,默认装载因子为总容量的2/3。ThreadLocalMap 自己的hash算法给每个ThreadLocal计算出一个hash值i,然后把对应的value存储在数组的table[i]位置。
hash值重复的怎么办?
当计算出的hash值已存在时,会对计算出的hash值再次进行rehash计算,直到找到一个未被占用的位置为止。
数组容量不够了怎么办?
当数组的数量大于总容量的2/3时,会进行脏key的回收(软引用对象被回收的entry),回收后数量>=总容量的1/2则进行扩容,扩容后的容量为原来的2倍大,并且需要对所有的值进行rehash(hash函数和数组容量有关)
看完ThreadLocalMap我们回头看看ThreadLocal到底持有什么东西
private final int threadLocalHashCode = nextHashCode();
/**
* The next hash code to be given out. Updated atomically. Starts at
* zero.
*/
private static AtomicInteger nextHashCode =
new AtomicInteger();
/**
* The difference between successively generated hash codes - turns
* implicit sequential thread-local IDs into near-optimally spread
* multiplicative hash values for power-of-two-sized tables.
*/
private static final int HASH_INCREMENT = 0x61c88647;
/**
* Returns the next hash code.
*/
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
ThreadLocal有一个不可变的成员变量threadLocalHashCode(可能重复),值由全局的AtomicInteger 生成。HASH_INCREMENT 是为了让哈希码能均匀的分布在2的N次方的数组里。
真令人惊讶,ThreadLocal中不存储任何和线程有关的属性,那么这个属性到底存储在哪里?
答案是存储在Thread中。
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
Thread中有两个成员变量,threadLocals 和inheritableThreadLocals ,数据类型都是ThreadLocal.ThreadLocalMap。
每当在线程下创建一个ThreadLocal,就会给ThreadLocal生成一个threadLocalHashCode。
public T get() {
Thread t = Thread.currentThread();
// return t.threadLocals;
ThreadLocalMap map = getMap(t);
if (map != null) {
// 根据this的threadLocalHashCode计算出下标,然后从数组中获取value
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
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方法时,会把相应的值存储到当前线程的threadLocals 中,可以理解为key=ThreadLocal value=value。调用get方法查询时,也会根据当前ThreadLocal的threadLocalHashCode值进行hash,然后在当前线程的threadLocals 中获取对应值
setget
ThreadLocal内存泄漏问题
由于ThreadLocalMap中的Entry对ThreadLocal是软引用,所有一旦ThreadLocal被垃圾回收后,Entry中的value将会无法被remove(好像扩容的时候会进行清理),从而导致内存泄漏。(只有ThreadLocal被回收了,Thead还没被回收才会发生)
解决方案:使用ThreadLocal时
finally{
threadLocal.remove()
}
网友评论