ThreadLocal俗称线程本地, 顾名思义,它提供的是封闭在线程的局部变量,也就是说只有当前线程才能设置、获取、移除等操作,并且它将随着线程的消亡而被GC。
原理图
查看源码之前,我们先看一个图
如图所示,每一个Thread线程对象中包含了一个ThreadLocalMap,这是一个简化版的hashmap的实现。所以自然的,ThreadLocalMap包含着一个散列表。散列表存放的是key/value形式的数据。key是ThreadLocal实例,value就是你要设置的数据。
当前你通过ThreadLocal去设置数据时,如:
threadLocal.set("lay");
1、根据threadLocal中包含的一个hashCode(标识当前threadLocal实例),计算出落在散列表上的索引位置;
2、然后把threadLocal作为key + 要存储的数据一起存储到散列表的节点上;
3、如果该节点已经存在数据了,那么往右找下一个节点,如果右边没有节点了,那么循环到索引为0的位置,以此类推;
4、如果没有充足的位置存放数据了,那么将进行扩容,并重新散列。
ThreadLocalMap是被组合在Thread对象里的,所以当线程消亡的时候ThreadLocalMap将失去引用,从而被GC,而ThreadLocalMap里面存放的数据,如果没有其它引用持有的话那么也一样会被GC回收。并且,外部无法直接访问ThreadLocalMap,而ThreadLocal类和Thread在同一个包下因而你仅可以通过它来访问,从而保证了ThreadLocalMap封闭在线程内部。
DEMO
我们从一个简单的DEMO入手
源码解析
首先构造了一个ThreadLocal静态变量,构造方法里面啥也没干
set()
点击进入set()方法
先获取了当前线程Thread对象,然后从Thread对象里面获取ThreadLocalMap对象。初次调用的话ThreadLocalMap为空,那么进入createMap方法创建一个。
进createMap看看怎么创建的
构造一个ThreadLocalMap的时候,把当前ThreadLocal的实例传入,并把值也传入,看看构造方法
构造方法先初始化了一个空的hash表,然后根据threadLocal的hashCode计算出索引的位置,再以threadLocal作为key创建了一个节点(Entry),设置大小为1,并设置扩容的阈值。这样一个初始化的ThreadLocalMap就构建完了。
我们回到set的时候判断ThreadLocalMap是否为空上来,如果它不为空直接调用ThreadLocalMap的set方法设置值。进入set方法
set方法有点长,原理比较简单。就是先根据threadLocal的hashCode计算出索引位置,然后比对threadLocal对象是不是当前这个对象,遍历整个hash表直到找到对象,或者完全找不到对象。前者直接赋值,后者创建一个节点。创建节点的话会判断需不需要扩容,如果需要的话重新进行hash计算。
get()
看完set方法,再看get方法就显得很简单了
一样是先拿到Thread中的ThreadLocalMap,如果Map为空,那么返回一个初始值。如果不为空,找到Entry节点,如果节点找了返回节点数据,否则返回初始值。
remove()
remove方法,一样很简单
找到ThreadLocalMap,调用remove方法,传入threadLocal实例。进入remove看看
遍历hash表,找到该key对象的Entry,然后做了清理操作。
总结
ThreadLocal实际上是包含了ThreadLocalMap的数据操作,而ThreadLocalMap组合在Thread中,随着Thread消亡。