【数据结构和算法】ThreadLocal的底层数据结构探究


一、ThreadLocal的概述

1、Thread对象有一个属性ThreadLocal.ThreadLocalMap,该数据结构底层为数组,用于存放当前线程的ThreadLocal实例。类似hashMap的实现,但hash槽位上不是链表。

2、ThreadLocal.ThreadLocalMap的hash槽位上是一个弱引用的指针。具体作用:

public void func1() {
        ThreadLocal tl = new ThreadLocal<Integer>(); //line1
         tl.set(100);   //line2
         tl.get();       //line3
}
View Code

当func1方法执行完毕后,栈帧销毁,强引用 tl 也就没有了,但此时线程的ThreadLocalMap里某个entry的 k 引用还指向这个对象。若这个k 引用是强引用,就会导致k指向的ThreadLocal对象及v指向的对象不能被gc回收,造成内存泄漏,但是弱引用就不会有这个问题(弱引用及强引用等这里不说了)。使用弱引用,就可以使ThreadLocal对象在方法执行完毕后顺利被回收,而且entry的k引用指向为null,此后我们调用get,set或remove方法时,就会尝试删除key为null的entry,可以释放value对象所占用的内存。

概括说就是:在方法中新建一个ThreadLocal对象,就有一个强引用指向它,在调用set()后,线程的ThreadLocalMap对象里的Entry对象又有一个引用 k 指向它。如果后面这个引用 k 是强引用就会使方法执行完,栈帧中的强引用销毁了,对象还不能回收,造成严重的内存泄露。

 注意:虽然弱引用,保证了k指向的ThreadLocal对象能被及时回收,但是v指向的value对象是需要ThreadLocalMap调用get、set时发现k为null时才会去回收整个entry、value,因此弱引用不能保证内存完全不泄露。我们要在不使用某个ThreadLocal对象后,手动调用remoev方法来删除它,尤其是在线程池中,不仅仅是内存泄露的问题,因为线程池中的线程是重复使用的,意味着这个线程的ThreadLocalMap对象也是重复使用的,如果我们不手动调用remove方法,那么后面的线程就有可能获取到上个线程遗留下来的value值,造成bug。

3、ThreadLocal自身有一个计数器,用于生产每一个threadLocal对象的hash值,该值会和ThreadLocalMap中的数据长度进行hash运算,确定threadLocal的在数组中的存储位置。

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM