什么是ThreadLocal?它是属于线程自己的小仓库。也就是在堆中创建一个线程自己才能访问到的对象,利用线程封闭来确保安全。
图片来自占小狼使用时会创建一个ThreadLocal静态变量,然后在方法中调用set,第一次调用set时会创建一个ThreadLocalMap对象,将其赋给线程的ThreadLocal.ThreadLocalMap threadLocals
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
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);
}
ThreadLocalMap是ThreadLocal的静态内部类,ThreadLocalMap里有一个Entry的静态内部类。这三者啥关系?存储key,value的是Entry,ThreadLocalMap封装Entry实现主要功能,ThreadLocal对ThreadLocalMap进一步封装,使得调用非常简便;
现在想想上面说的当你第一次调用set后,堆中是什么情况?
- 堆中一个ThreadLocal实例,它是静态的所以只会存在一个,多个线程共享它。
- 每个线程都会有一个自己的ThreadLocalMap实例,分析一下它们的可达性,只有本线程的threadLocals变量指向它,也就是说当线程执行完毕,线程的ThreadLocalMap实例会被GC清除。
- 每个线程还会在堆中创建只有自己可达的Entry数组对象,ThreadLocalMap里的table指向它,它的key是ThreadLocal实例,所以当线程销毁,Entry仍有可达性,ThreadLocal是静态的那Entry就永远不会被销毁了,OOM!所以引入了弱引用WeakReference,将Entry里对ThreadLocal引用设为弱引用,当只有弱可达性时,Entry会被清理;
如上面所说的,那么要是Thread一直不销毁呢?仍然会发生OOM!
如何防止?ThreadLocal在set,get方法调用时都会主动清理Entry[]中key为null的entry;在使用时一个良好的习惯便是主动清理,
try{
//do
}finally{
threadLocal.remove();
}
这样便能避免OOM;
我们从线程安全方面想想,为什么不直接在方法中创建一个仓库实例,它也是利用线程封闭,安全,不就不用这么麻烦了吗?那么当你想在其它地方获取存储值时只能将它传递过去,这就相当于你将这个实例发布出去了,这就存在安全隐患,而且传递来传递去非常不方便;我们再来看看ThreadLocal的设计,如前面分析的一样,它非常巧妙,让人感叹啊!
其他方面比如ThreadLocal利用hash来定位,用线性探测法来解决hash冲突。
网友评论