ThreadLocal 是 jdk 中一个非常重要的工具, 它可以控制堆内存中的对象只能被指定线程访问, 如果你经常阅读源码, 基本在各大框架都能发现它的踪影. 而它最经典的应用就是 事务管理 , 同时它也是面试中的常客.
原理
我们知道, 堆内存是共享的, 为什么 ThreadLocal 能够控制指定线程访问呢? 如图:
调用 ThreadLocal 的 get 方法.
获取当前线程 t1.
获取 t1 的成员变量 ThreadLocalMap .
根据 ThreadLocal 的 hashcode 计算出 ThreadLocalMap 中 Entry[] 数组的索引.
返回索引位置的值.
这样我们就很容易理解了, 为什么只有当前线程才能获取到某些值, 因为这是这些值都直接保存在当前线程的成员变量 ThreadLocalMap 中, 而 ThreadLocal 在这个过程中充当的角色则是提供它独一无二的 hashcode 值, 这样我们就能计算出我们保存的值在 ThreadLocalMap 的位置.
源码分析
我们从构建一个 ThreadLocal 到调用它的 set,get 方法完整的分析一遍它的源码.
构造器
当我们使用 new ThreadLocal<>() new 一个 ThreadLocal 对象时, 它初始化了一个成员变量 threadLocalHashCode , 这个成员变量代表当前 ThreadLocal 的 hashcode 值, 而它肯定是唯一的:
ThreadLocal 内部有一个静态 hashCode 生成器 nextHashCode .
每次新 new 一个 ThreadLocal 对象, 调用这个生成器同步方法获取 hashcode.
因为依赖于静态成员变量 nextHashCode 的关系, 所以它的 hashcode 肯定唯一!
- set(T t)
- publicvoidset(Tvalue){
- Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t);if(map !=null) map.set(this,value);elsecreateMap(t,value);
- }
获取当前线程 t.
从 t 中获取 ThreadLocalMap map.
如果 map 不为空, 将当前值 value 放入 map.
如果 map 为空, 新建一个 ThreadLocalMap 放入线程 t.
ThreadLocalMap 是 ThreadLocal 中的内部类, 它的结构如下:
publicclassThreadLocalMap{ staticclassEntryextendsWeakReference<ThreadLocal<?>>{Objectvalue;Entry(ThreadLocal k,Objectv) {super(k); value = v; } }privateEntry[] table;privateint size =0;privatestaticfinalintINITIAL_CAPACITY=16;privateint threshold;// Default to 0}
类似于 ArrayList 内部的构造, 它内部有一个 Entry 数组 table, 并且 Entry 继承自弱引用, 所以每一个 Entry 中保存着两个值, ThreadLocal , value ,value 既是我们要保存的值.
接着, 我们回过头详细分析第三步, ThreadLocalMap 的 set 方法:
privatevoidset(ThreadLocal key, Objectvalue){ Entry[] tab = table;intlen = tab.length;inti = key.threadLocalHashCode & (len-1);// 1for(Entry e = tab[i]; e !=null;// 2e = tab[i = nextIndex(i, len)]) { ThreadLocal k = e.get();if(k == key) {// 3e.value=value;return; }if(k ==null) {// 4replaceStaleEntry(key,value, i);return; } } tab[i] =newEntry(key,value);// 5intsz = ++size;if(!cleanSomeSlots(i, sz) && sz>= threshold)// 6rehash(); }
根据 ThreadLocal 的 hashCode 计算出在 entry 中的索引 i.
取出 i 对应的 Entry 值 e.
如果 e 的 key 等于当前 ThreadLocal, 代表已经有一个一样的 ThreadLocal 在这个 entry 设值, 直接替换这个 entry 上的 value.
e 上面的 ThreadLocal 为 null, 代表垃圾收集器准备回收这个 Entry 了, 重新计算数组大小, 重新 hash.
i 位置还没有初始化 (第一次 set 这个 ThreadLocal), 直接将 value 放到 i 的位置.
扩容 Entry 数组.
- get()
- publicTget() {
- Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t);if(map !=null) {
- ThreadLocalMap.Entry e = map.getEntry(this);if(e !=null) {
- @SuppressWarnings("unchecked")T result = (T)e.value;returnresult;
- }
- }returnsetInitialValue();
- }
获取当前线程.
从当前线程中获取 ThreadLocalMap
从 ThreadLocalMap 中找出 ThreadLocal 对应的 Entry.
如果 Entry 不为 null, 直接返回 Entry 中的 value
返回初始值.
其中, ThreadLocalMap 的 get(ThreadLocal tl) 如下:
它和我们一开始的分析一样, 根据 ThreadLocal 的 hashcode 成员变量计算出索引位置 i, 得到 Entry. 这里同样有特殊情况, 如果得到的 Entry 的 key 和当前 ThreadLocal 不相等, 代表这个 Entry 将被垃圾收集处理, 调用 getEntryAfterMiss rehash, 计算数组大小.
注意事项
从上面的代码分析中, 我们知道, ThreadLocalMap 的生命周期和当前线程同步, 如果当前线程被销毁, 则 map 中的所有引用均被销毁. 但如果当前线程不被销毁呢 (线程池, tomcat 处理请求等)?Entry 中保存了 ThreadLocal 的弱引用以及 value,gc 时可能清理掉 ThreadLocal, 而这个 value 确再没有访问之地, 这个时候就会造成内存泄漏!
所以我们需要手动调用 remove 方法清理掉当前线程 ThreadLocalMap 的引用!
总结
ThreadLocal 中真正保存的值还是在线程的 ThreadLocalMap 中, ThreadLocal 只是使用它的 hashcode 值充当中间计算变量.
ThreadLocalMap 内部使用一个 Entry 数组保存数据.
ThreadLocal 可能出现内存泄漏的情况, 最好手动调用 remove 方法.
写到最后
我自己收集了一些 Java 资料, 里面就包涵了一些 BAT 面试资料, 以及一些 Java 高并发, 分布式, 微服务, 高性能, 源码分析, JVM 等技术资料
想要获取的同学可以加 Java 群: 171662117 即可免费获取以上内容资料
部分如下:
今天免费分享 免费分享!
转发 !
转发 !
来源: http://www.jianshu.com/p/62ed6086f28b