ThreadLocal大家应该都经常用到,比如多租户模式,动态配置多数据源,源码就不逐句分析了,想必大家都看过,但是具体相关问题,有些也和我一样比较模糊,今天做下记录,带着问题看源码
1.为什么ThreadLocal能看到做到线程隔离
直接上源码看注释(ThreadLocal 部分源码)
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t); #这里获取Map
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this); #这里的key并不是thread对象,而是当前的ThreadLocal对象
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals; # 获取的map为当前线程的实例变量,所以不同线程,互相不影响
}
线程的源码

总结: 为什么不同线程互不影响,能做到线程隔离,因为虽然ThreadLocal是公共变量,但是ThreadLocal调用get,set 方法时,最后操作的都是当前线程的实例变量的ThreadLocalMap,不同线程操作的都是不同线程自身的实例变量ThreadLocalMap,当然是互不影响,同时,虽然是不同线程,但是操作时的ThreadLocalMap对象的key都是当前ThreadLocal这个对象。
2.ThreadLocalMap 只是entry数组,没有加链表,怎么防止hash冲突的?
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.
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) { #如果有hash冲突,直接到下标下一个
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
/**
* Increment i modulo len.
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0); #返回数组下一个位置
}
总结:
当出现hash冲突时,直接放到数组下一位
3.为什么会出现内存泄漏的问题
# ThreadLocalMap 的entry源码
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}

TreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal不存在外部强引用时,Key(ThreadLocal)势必会被GC回收,这样就会导致ThreadLocalMap中key为null, 而value还存在着强引用,只有thead线程退出以后,value的强引用链条才会断掉。但如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链,永远无法回收,造成内存泄漏。
4.为什么不用这个key不用强引用
Key使用强引用:引用ThreadLocal的对象被回收了,ThreadLocal的引用ThreadLocalMap的Key为强引用,并没有被回收,如果不手动回收的话,ThreadLocal将不会回收那么将导致内存泄漏。
Key使用弱引用:引用的ThreadLocal的对象被回收了,ThreadLocal的引用ThreadLocalMap的Key为弱引用,如果内存回收,那么将ThreadLocalMap的Key将会被回收,ThreadLocal也将被回收。value在ThreadLocalMap调用get、set、remove的时候就会被清除。
比较两种情况,我们可以发现:由于ThreadLocalMap的生命周期跟Thread一样长,如果都没有手动删除对应key,都会导致内存泄漏,但是使用弱引用可以多一层保障:弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set,get,remove的时候会被清除。
5.那么如何有效的避免呢?
事实上,在ThreadLocalMap中的set/getEntry方法中,会对key为null(也即是ThreadLocal为null)进行判断,如果为null的话,那么是会对value置为null的。我们也可以通过调用ThreadLocal的remove方法进行释放!

本文深入解析ThreadLocal的工作机制,包括线程隔离原理、hash冲突处理方式、内存泄漏原因及预防措施,帮助理解ThreadLocalMap内部结构。
265

被折叠的 条评论
为什么被折叠?



