解析ThreadLocal

938 阅读3分钟

ThreadLocal是在开发中相对比较常见的工具类了,可以在多线程环境下保证线程安全,其基本原理就是给每一个对象都分配一个属于当前线程的私有对象,这样线程之间拿到的对象就不会相互冲突了。

可以想象成,如果多个人公用一个厕所的时候,因为厕所的一些限制,有很多的人都不能冒然的使用厕所,如果每一个人来的时候都给他一个单独的厕所这样相互之间就不会打扰了,缺点的话也很明显可以看出来就是浪费资源。所以在用完的时候要记得销毁资源。

有的人可能会问你再什么时候下使用ThreadLocal呢?

  • 在Spring的事务中,默认内部用的是ThreadLocal给每一个线程分配一个单独的连接,不过这是内部已经封装好的了,不算是我们自己写的
  • 在Web应用中,有的时候要对一些请求做单独的处理,响应做额外的处理,每个人的写法都不一样,我有见过一些人在每一个请求的时候对应一个线程,并且对每个请求都有一些ThreadLocal相关的变量,存储请求的信息,以便在后续的处理中再从ThreadLocal中去拿
  • 比较常见的例子,应该是SimpleDateFormat了,这个对象在多线程下会出现一定的问题,一般在高并发的场景下,都会使用ThreadLocal给每个线程分配一个SimpleDateFormat对象。

知道了原理在使用的时候考虑下,或者在对应的场景下想想能不能用ThreadLocal去做。

内部结构

只是知道了其基本使用的话,相对还是不够,其内部的数据结构也很有意思,在看过之后才发现其中的巧妙。

  • 每个Thread对象内部都有一个ThreadLocal.ThreadLocalMap属性,属性名为threadLocals,在创建线程之后可以直接通过t.threadLocals访问

image-20190513101303375

当调用ThreadLocal的set方法时:

  • 获取当前Thread的ThreadLocalMap属性,如果当前线程的ThreadLocalMap为空的话,会进行初始化并赋值,如果不为空,则直接赋值

当有多个ThreadLocal对象时候,其实每个线程内部的ThreadLocalMap属性里面的Entry数组,对应的下标为ThreadLocal的hashCode进行取余,然后再构建Entry对象。

其实就是一个线程内部可以存储多个ThreadLocal给的线程私有对象,只不过ThreadLocal对象是访问那些线程私有对象的入口。

内存泄漏问题

关于ThreadLocal如果不进行Remove是否会导致内存泄漏?

通过上面我们知道Thread中的ThreadLocalMap属性中的Entry表都是弱引用的对象,其引用为ThreadLocal对象,那么弱引用对象一般在进行垃圾回收的时候都会被回收掉。

ThreadLocal在没有外部强引用时,发生GC时会被回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。

记得在使用ThreadLocal完毕之后,调用ThreadLocal.remove方法。

最后

ThreadLocal也算是一个比较经典的知识点了,通过它可以问出

  • 引用类型
  • Map的数据结构
  • 线程安全
  • 实际场景中的用法

希望对你有帮助

参考