作用

为了让每个线程都有一份单独的实例,同一个线程里的所有方法都是共享的,而多线程不共享。

分析

image.png
从源码可以知道,当ThreadLocal变量set一个值时,过程如下:

  • 获取当前线程
  • 从当前线程中拿到成员变量map
  • 以ThreadLocal对象为key,保存到map里

这也就解释了为什么是线程隔离的,因为在两个不同的线程里set和get对象,所用的map是在各自的Thread对象里,当然在一个线程中get不到另一个线程set的对象。

再来看一下这个map
image.png
map底层是由Entry数组组成,而这个是WeakReference的子类,调用了父类的构造方法,说明Entry数组中的key是使用弱引用指向ThreadLocal对象的。内存图如下:
ThreadLocal理解 - 图3

  • 为什么要使用弱引用呢?

弱引用的特点是gc时会把对象回收,也就是说对象只能存活到下一次gc前。使用弱引用的是为了让ThreadLocal对象的生命周期和线程生命周期解绑,试想若是强引用,即使你把tl置空,ThreadLocal对象依然跟线程存在引用关系而不能被回收,假如使用线程池,线程不销毁,那么势必造成内存泄漏。当使用弱引用后,这种情况就不会发生。

  • 弱引用带来的问题

gc后,key为null,但是value依然强引用指向一个对象,若线程不销毁,还是会造成内存泄漏。

  • 怎么解决?

1、在使用完thread变量前,显示调用remove方法将key为null的value置空。
2、JDK建议ThreadLocal定义为private static,此时tl是静态变量,在进程结束时才被回收,ThreadLocal对象也就不会回收了。

使用场景

  • spring中事务的实现

在一个事务中,可能有多个方法都需要使用数据连接,必须保证它是同一个连接,此时可以使用ThreadLocal

  • SofaTracer中的tracerId ?


补充

四种引用

强引用 一直存活,直至内存不足
软引用 内存不足时会被回收 适用于高速缓存,例如从数据库读取的大数据,为避免多次读取,可将它置于内存中,用软引用指向它,内存不足自动回收就好了
弱引用 只能存活到下一次GC前
虚引用 随时被回收 多用于管理堆外内存,例如一个堆内对象指向堆外内存上的某个地址,这个对象同时被虚引用指向,当这个对象被回收前,会加入到一个队列,而垃圾回收线程一旦发现此队列有对象,会让jvm去处理它所指向的堆外内存。

ThreadLocalMap是怎么解决哈希冲突的?

image.png
开放地址法,即找下一个索引,不管刚刚产生的哈希值。因为map以ThreadLocal是键,一般来说ThreadLocal数量很少,找到一个不哈希冲突的索引比较容易。