[toc]
作用
ThreadLocal提供了线程内的局部变量,相比较锁,他是空间换时间的思想,使得每个线程访问属于自己的独立副本。
这个类给线程提供了一个本地变量,这个变量是该线程自己拥有的,在该线程存活的ThreadLocal实例访问的时候,保存了这个变量副本的引用,如果当线程消失的时候,所有的本地实例都会被GC建议使用ThreadLocal最好使用private static修饰的成员。
注:当线程中某个ThreadLocal被GC掉(线程没有消亡)此时Entry中的key值将称为null,而value值还存在,知道线程消亡之前,Entry中的value都无法被gc回收,这就可能造成内存泄漏。为了解决这个问题,源码中set、get、remove、rehash方法执行时都会清除这些key为null的entry,当我们调用set、get方法时,都有可能将Entry的key为null的对象给清除掉(前提是触发了key是null的操作,如果没有触发这个操作也是不会清除key是null的情况),但是还会出现内存泄漏问题。
为了避免内存泄漏可以有以下两种方式:
- 使用完手动执行remove()
- 将ThreadLocl定义为private static,这样可以随着线程一起消亡。
结构
image每个Thread维护一个TreadLocalMap,ThreadLocalMap中存储的是一个Entry[]tab数组,Entry中的key是ThreadLocal,value是ThreadLocal中设置的值。
Thread与ThreadLocal的关系
ThreadLocal的set方法:
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程对应的ThreadLoclMap
ThreadLocalMap map = getMap(t);
//如果map不是null那么就set值,如果是null那么先创建,然后在设置值
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
//获取当前线程的ThreadLocalMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//给Thread创建一个ThreadLocalMap
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
//下面是Thread对ThreadLocalMap的定义
ThreadLocal.ThreadLocalMap threadLocals = null;
其实Thread类中有一个ThreadLocalMap为null的变量,如果Thread返回引用是null,就调用createMap方法,这就为Thread创建了一个能保存本地线程的map,当第一次使用ThreadLocl的时候,通过getMap()得到的ThreadMap必定是null所以需要调用createMap。
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
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);
}
//设置map扩容的阈值,当Entry[]中元素达到这个阈值时候开始扩容,阈值为Entry[]长度的2/3
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
当达到最大长度的2/3时候就会扩容
可能会出现的问题
ThreadLocal中的Entry的key值是弱引用,代表他很快被GC掉。
弱引用:不会影响对象的GC(当对象只有弱引用存在的时候,只要GC就会被回收掉)
个人认为:如果是强引用,ThreadLocal不在使用时,GC时候ThreadLocalMap中还存在强引用,导致无法被GC回收,如果弱引用ThreadLocalMap就不算强引用了,就可以被GC回收,map的后续操作中,也会逐渐把对应的过时条目(key==null)清理出去,避免内存内存泄漏,Entry的定义如下:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
ThreadLocalMap使用弱引用key,如果一个ThreadLocal没有外部强引用,那么系统gc的时候这个ThreadLocal必然会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果线程不结束,这些key为null的Entry的value就会一直存在一条强引用链,就会造成内存泄漏。
ThreadLocal之间是如何区分的
//每一个对象都有一个hashCode来标志自己的唯一性
private final int threadLocalHashCode=nextHashCode();
//下一个要给出的哈希码。原子更新。从零开始,原子类,保证线程安全,保证每一个hashCode唯一,并且是静态的
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.
* hash增量,每次获得hash之后加上这个数
*/
private static final int HASH_INCREMENT=0x61c88647;
/**
* 返回下一个hashCode
*/
private static int nextHashCode(){
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
比如第一个ThreadLocal的hashCode就是0,那么在定义一个他的hashCode的基础加上HASH_INCREMENT,这样map中他们的hashCode不一样,但是这个时候虽然hashCode不一样,但是计算下标有可能一样,造成hash处重复,在ThreadLocalMap里面解决hash冲突是线性探查法来解决,当i下标有值的时候则找到i+1处,然后依次往下推。
总结
调用set或者get时候,ThreadLocal会有可能触发清除key为null的值,使用线程池的时候,我们应该在线程使用完ThreadLocal的时候自觉的调用remove方法清空Entry。
废弃的ThreadLocal所绑定的对象引用,会在以下情况被清理:
- Thread结束时
- 当Thread的ThreadLocalMap的threshold超过最大值(也就是触发rehash扩容时候)
- ThreadLoclMap中存放一个ThreadLocal,hash算法之后获取的Entry中的key为null时
- 手动通过ThreadLocal的remove方法或者set(null)
网友评论