ThreadLocal部分源码分析

结构演进

早起JDK版本中,ThreadLocal内部结构是一个Map,线程为key,线程在“线程本地变量”中绑定的值为Value。每一个ThreadLocal实例拥有一个Map实例。(Key是线程,Value是值)

JDK8中,ThreadLocal内部结构发生了演进,虽然还是Map,但是拥有者变成了Thread实例,每一个Thread实例拥有一个Map实例。Map中的key变为ThreadLocal实例。(Key是ThreadLocal,Value是值)

新版ThreadLocalMap如图:

image

每一个线程在获取本地值时,都会将ThreadLocal实例作为Key从自己拥有的ThreadLocalMap中获取值,别的线程无法访问自己的ThreadLocalMap实例,达到相互隔离的目的。

新版优势(减少了内存消耗)

(1)ThreadLocalMap存储的键值对数量减少。早期版本的数量与线程个数强关联;新版的Key是ThreadLocal实例,会比线程数少。

(2)早期版本ThreadLocalMap拥有者为ThreadLocal,在线程销毁后,ThreadLocalMap仍然存在;新版的ThreadLocalMap拥有者为Thread,Thread实例销毁后,ThreadLocalMap也会随之销毁,减少内存消耗。

Entry的Key需要使用弱引用

为什么不直接使用ThreadLocal实例作为Key呢?

public void funcA() {
    ThreadLocal local = new ThreadLocal<Integer>();
    local.set(100);
    local.get();
}

当线程执行funA方法时,先新建一个local实例,这是强引用,调用set方法后会在内部新建Entry实例,Key是弱引用包装指向的local实例。当线程执行完A方法后,方法栈帧被销毁,强引用local的值也没有了,但此时线程的ThreadLocalMap对应Entry的Key引用还指向的ThreadLocal实例都不能被GC回收,这将造成内存泄漏问题。

弱引用:对象只能生存到下一次垃圾回收之前。

由于ThreadLocalMap中Key是弱引用,下次GC发生时,可以把那些没有强引用指向的ThreadLocal回收。并且key被回收后,其Entry的key值变为null。后续ThreadLocal的get、set方法被调用时,就会清除这些Key为null的Entry,完成内存释放。

使用state final修饰ThreadLocal对象

ThreadLocal实例作为ThreadLocalMap的Key,针对一个线程内的所有操作是共享的,使用static修饰ThreadLocal节约空间。

Re

《Java高并发核心编程》

Tags: