定义:
这个类给线程提供了一个本地变量,这个变量是该线程自己独立拥有的
开放思维:
如果我们现在需要实现一个功能,不同的线程拥有自己不同的值,该如何实现呢?
我们可能会想到把每一个线程作为key,需要保存的变量值作为value,存入一个大的HashMap中,这样就可以实现干扰。
那么请思考一下,这样实现的缺点是什么呢?
1:如果有大量的请求线程,那么HashMap得需要占用多大的内存?需要扩容多少次?
2:随着线程生命周期结束,value值不会回收,会一直占用内存。
那有没有更好的解决办法呢?
针对问题2,可以采用将每一个局部变量值保存在其对应的线程中的办法来实现,这样当该线程生命周期结束后,其内部保存的变量值也会随之被gc。
1、ThreadLocalMap键值对数量为ThreadLocal的数量,一般来说ThreadLocal数量很少,相比在ThreadLocal中用Map键值对存储线程共享变量(Thread数量一般来说比ThreadLocal数量多),性能提高很多。
其实在jdk1.3之前就是采用的在ThreadLocal中维护一个Map,以Thread为key,value为值,进行存储每一个线程需要保存的本地变量的。
ThreadLocal源码:
构造函数
set函数
ThreadLocal的set(T value)方法实质上是转到了当前线程的ThreadLocalMap的set(ThreadLocal<?> key, Object value)方法
getMap()函数
Thread类中成员变量ThreadLocalMap初始值为null
创建ThreadLocalMap对象
接下来看ThreadLocalMap的构造函数
1:初始化ThreadLocalMap的table容量为size为16.
2:计算第一个key的hashCode
3:实例化一个Entry实例作为节点,并以该传入的ThreadLocal作为key,value作为值
4:实例化的Entry节点存入table中
5:设置table初始阈值即 16 * 2 / 3
其实以上流程可以理解为初始化一个HashMap,并向其中设置了第一个节点,节点比较特殊,每个节点中都保存了当前的ThreadLocal引用,以及当前设置的value值。
接下来详细看下 ThreadLocalMap 的set方法 set(ThreadLocal<?> key, Object value)的实现
参数有两个,一个是以ThreadLocal类实例作为key,一个是Object类型的value
一步一步来分析看
1:首先获取Map内的Entry数组,即Map中存放Entry节点的数组引用
2:获取当前Entry数组的长度(可能经过了扩容,就不是初始值16了)
3:计算此次ThreadLocal 参数 即key的hashCode值 i,采用与的方式计算,据说效率更高。
4:开始遍历,取出Entry数组 i 下标位置的Entry引用。作null判断
5:假如不为null,说明tab[i] 对应位置已经有节点了,即存在hash冲突了。我们看下他是怎么处理的
获取已有节点Entry中的key值 ThreadLocal对象引用是否等于此次需要产生hash冲突的key
如果等于,说明两次设置的是同一个ThreadLocal 的值,就直接覆盖掉,退出循环
如果已有节点的key引用等于null,则说明存在了key为null的情况,就会调用replaceStaleEntry方法去重置该key为null的Entry,并会重置其value值为新需要设置的value值。
如果以上都不是,那么就存在了hashCode函数自身的问题,即把两个不同的ThreadLocal实例key,计算出了相同的hashCode值,此处给出的解决方案是通过遍历相邻的下一个下标,取出下一个下标对应的Entry,如果不为null,就重复计算下一个相邻的下标,知道找到Entry 为null的下一个下标对应的位置。就开始退出循环,直接新增Entry节点,然后插入数组中。
6:
以上就是set方法的详解。接下来看一下 ThreadLocal 的 get方法
1:获取当前线程
2:获取当前线程内的ThreadLocalMap对象
3:调用ThreadLocalMap的getEntry方法(ThreadLocal<?> key),参数就是当前ThreadLocal对象。
4:getEntry方法即使根据传入的ThreadLocal对象作为key,计算其hashCode值,再从Entry数组中获取到该Entry,如果该Entry不为null且该Entry对应的key就是传入的key,那么就直接返回该Entry,否则就存在hash冲突,进入getEntryAfterMiss方法进行处理找到冲突后存放该key的位置。
获取到Entry之后,就直接取出该Entry中保存的value值即可。
如果entry为null,则进入 return setInitialValue方法处理
初始值就是null
关于ThreadLocalMap<ThreadLocal,Object>弱引用问题:
当线程没有结束,但是ThreadLocal已经被回收,则可能导致线程中存在ThreadLocalMap<null,Object>的键值对,造成内存泄露。(ThreadLocal被回收,ThreadLocal关联的线程共享变量还存在)。
虽然ThreadLocal的get,set方法可以清除ThreadLocalMap中key为null的value,但是get,set方法在内存泄露后并不会必然调用,所以为了防止此类情况的出现,我们有两种手段。
1、使用完线程共享变量后,显示调用 ThreadLocal.remove 方法清除线程共享变量;
2、JDK建议ThreadLocal定义为 private static,这样ThreadLocal的弱引用问题则不存在了。
ThreadLocal 是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。
内存泄漏问题(摘录自:深入剖析ThreadLocal实现原理以及内存泄漏问题):
最后附上大致的ThreadLocal关联图