源码解析
ThreadLocal的内部主要通过ThreadLocalMap来实现的,我们先看这个Map
ThreadLocalMap
ThreadLocalMap并没有实现Map接口,也没有集成AbstractMap等类,而是纯纯的一个内部实现。
基本的内部字段如下:
/** Map初始容量 */ 默认entry数量0 */
|
再看set方法
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table; //根据ThreadLocal的hash值计算entry索引 //获取当前ThreadLocal对象 //替换无效的entry为新的entry //size+1 //清理无效槽位,并判断是否大于阈值 rehash(); //扩容方法 private void rehash() { //清理失效的entry |
ThreadLocalMap并不想hashMap的数据结构一样,他是一个很单纯的Entry对象数组,entry中包含ThreadLocal对象和值,并没有next节点。
根据ThreadLocal对象寻找索引的计算和hashMap类似,也是hash值和容量-1做与运算。
在ThreadLocalMap中存在多种清理无效entry节点的方法,因为Entry继承了WeakReference对象,WeakReference是弱引用的标志,如果系统触发了GC并且当前的没有强引用关联,则会回收这个弱引用,所以方法会多出很多清理无效节点的方法。
扩容机制比较简单,容量扩充2倍,阈值根据新容量重新计算threshold = len * 2 / 3;
我们再看ThreadLocal的set方法:
public void set(T value) { //获取当前线程 ThreadLocalMap map = getMap(t); //如果map不是空,则直接放入值 //创建map void createMap(Thread t, T firstValue) { //new一个对象,并赋值给线程对象中的threadLocals字段中 |
可以得知,ThreadLocal是通过Thread对象中的threadLocals字段来实现的,所谓的线程本地变量,都是有Thread来控制,Thread获取当前线程,Thread包含当前的ThreadLocalMap对象,支持ThreadLocalMap实现线程本地变量。
使用ThreadLocal注意问题:
- 大部分基于web容器运行的实例,都会存在线程池,比如tomcat,每次请求会从线程池中拿取线程进行请求,此时,ThreadLocal使用后必须进行清除,否则线程重用导致数据问题
- 内存泄漏,使用过后一定要释放,否则长期驻留内存,虽然ThreadLocal的key是弱引用,但是会增加垃圾回收Value的时间,并且强引用关联,是无法回收的。