by shihang.mai
1. 线程隔离原理
1.1 原理解析
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}

Thread类有一个类型为ThreadLocalMap
的变量,观察ThreadLocal实例的set方法,可以看到当set值时,其实是Thread的ThreadLocalMap,向里面set值。
下面举例说明
- 有ThreadLocal变量a、b
- 线程A调用a.set(),b.set()。线程B调用a.set(),b.set()

- 线程A调用a.set和b.set,线程B调用a.set和b.set,都会先获取当前线程的threadLocals,然后将值设置进去,关系图如上
- 而当前线程调用a.get或者b.get,都先获取threadLocals,然后通过当前的ThreadLocal实例,获取到对应的值
操作的都是线程自身的变量,做到线程隔离
1.2 ThreadLocalMap是ThreadLocal的内部类意义
注释写得很清楚了,ThreadLocalMap 就是为维护线程本地变量而设计的,只做这一件事情。
1.3 为什么key是ThreadLocal,而不是Thread对象
- 如果key是Thread对象的话,那么我再保存一个另外的值,不就把原来的值覆盖了吗,显然不行。
- 如果要再保存另外一个值,直接新建一个key,即新建ThreadLocal即可
1.4 ThreadLocal数据结构
ThreadLocalMap is a customized hash map suitable only for maintaining thread local values
注释也写得很清楚了,就是HashMap。但是遇到hash冲突怎么办呢,这个和hashmap有啥区别呢
- hashmap由数组+链表+红黑树组成。而ThreadLocalMap没链表也没红黑树
- 当ThreadLocalMap遇到hash冲突时,用的开放地址法,即看看相邻位置有没空位,有就加入。没就一直找到空的,把元素放进去。元素个数超数组长度就扩容
1.5 key设计成弱引用意义
为了尽最大努力避免内存泄漏,而不是直接避免了内存泄漏
因为只有当ThreadLocal对象只被WeakReference引用时,才会被gc回收。当还有强引用指向ThreadLocal对象,gc是不会回收的

问题就来了:那既然ThreadLocal对象有强引用,回收不掉,干嘛还要设计成WeakReference类型呢
- 按我们的使用习惯,当ThreadLocal = null 时,就意味着ThreadLocal要被回收,如果设计成强引用,那么这个语义就没了。
- 如果Threadlocal对象一直有强引用,就有内存泄漏风险,所以我们手动调用ThreadLocal.remove()
2. 子线程获取父线程设置的本地变量
使用InheritableThreadLocal
即可
static InheritableThreadLocal a =new InheritableThreadLocal();
public static void main(String[] args) {
a.set("a");
Msh msh = new Msh();
msh.start();
}
static class Msh extends Thread{
@Override
public void run() {
System.out.println(a.get());
}
}
Thread类有一个类型为ThreadLocalMap的变量inheritableThreadLocals
- 当我们使用InheritableThreadLocal,无论get或者set都会设置Thread的变量inheritableThreadLocals
- 父线程调用set,会将值保存到inheritableThreadLocals,父线程创建子线程时,会将自身的inheritableThreadLocals重新复制一份给子线程的inheritableThreadLocals,这样子线程就获取到了父线程设置的本地变量
3. 内存泄漏

前提:线程一直保留,即使用线程池
- 如果ThreadLocal不存在外部强引用时,Key会被GC回收,这样就会导致ThreadLocalMap中key为null, 而整一个Entry还存在强引用,即
key为null,但是value还在
。 - GC永远无法回收,造成内存泄漏
- 这种key为null,对应的value在下一次ThreadLocalMap调用set(),get(),remove()的时候会被清除
所以:内存泄漏的根源是由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用
网友评论