难倒无数程序员的 ThreadLocal 原理,就这样被美团大牛轻松讲透彻
今日分享开始啦,请大家多多指教~
什么是 ThreadLocal?
ThreadLocal 是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰。
ThreadLocal 怎么使用?
ThreadLocl 使用比较简单,主要有三个方法:get()、set()、remove()
相信不用我多说各位小伙伴也知道是什么意思
ThreadLocal 底层原理
点开 ThreadLocal 的 set()方法
首先是获取到当前线程
调用 getMap(t)方法获取到 ThreadLocalMap
如果 map 不为 null 则进行 set 操作,如果为 null 则进行创建 map 操作
点开 get()方法
跟 HashMap 差不多,小伙伴们自己看看源码绝对能懂
点开 remove()方法
跟 HashMap 差不多,找到索引,遍历又对因 key 就删除
细心的小伙伴可能就发现了这三个方法中都需要去获取:当前 Thread 和 ThreadLocalMap
那么这是为什么呢???
我们接着看源码,点开 getMap()方法
可以发现,ThreadLocalMap 获取的是 Thread 中的一个对象
相信到这大家就明白了 ThreadLocal 是怎么做到各个线程互不干扰的吧。
因为获取到的是当前线程的 ThreadLocalMap,各个线程所以互不干扰。
小伙伴们呢可别觉得已经掌握了,还没完呢,这才哪到哪,我们接着看
打开 ThreadLocalMap 的 set 方法
看过上面源码,熟悉 HashMap 的小伙伴们可能就发现了一个熟悉的身影——>Entry
但这个 Entry 就跟 HashMap 的有所不同,它继承了 WeakReference<ThreadLocal<?>>
WeakReference:弱引用
而上面还有段代码不知道小伙伴们注意到没:tab[i] = new Entry(key, value);
这段代码意味着什么相信也不用我多说了吧,就是将 key,value 封装为 Entry 节点;再根据 map.set(this, value);这段代码可知,Key 为当前的 ThreadLocal 对象,value 为我们要 set 进来的值。
但是这里又有所重点:那就是对 key 调用了 super(k),这里我暂且不多说,涉及到 ThreadLocal 的一个经典面试题,后面会进行详细讲解,小伙伴们要有耐心继续看哦!!!
到此基本结束,不要奇怪,ThreadLocal 其实并不是太难,那我们先来做个总结!
总结
我们 set 进去的值,最终是放在了当前线程的 ThreadLocalMap 中,并不是存在 ThreadLocal 上,ThreadLocal 可以理解为只是 ThreadLocalMap 的封装,传递了变量值。 ThrealLocal 类中可以通过 Thread.currentThread()获取到当前线程对象后,直接通过 getMap(Thread t)可以访问到该线程的 ThreadLocalMap 对象。
每个 Thread 中都具备一个 ThreadLocalMap,而 ThreadLocalMap 可以存储以 ThreadLocal 为 key ,Object 对象为 value 的键值对。
ThreadLocal 相关面试题
ThreadLocal 如何解决 Hash 冲突?
与 HashMap 不同,ThreadLocalMap 结构非常简单,没有 next 引用,也就是说 ThreadLocalMap 中解决 Hash 冲突的方式并非链表的方式,而是采用线性探测的方式。所谓线性探测,就是根据初始 key 的 hashcode 值确定元素在 table 数组中的位置,如果发现这个位置上已经被其他的 key 值占用,则利用固定的算法寻找一定步长的下个位置,依次判断,直至找到能够存放的位置。
使用 CAS,每次增加固定的值,所以采用的是线性探测法解决 HasH 冲突。
经典 CAS
ThreadLocal 内存泄漏问题及解决办法
ThreadLocal 在 ThreadLocalMap 中是以一个弱引用身份被 Entry 中的 Key 引用的,因此如果 ThreadLocal 没有外部强引用来引用它,那么 ThreadLocal 会在下次 JVM 垃圾收集时被回收。这个时候 Entry 中的 key 已经被回收,但是 value 又是一强引用不会被垃圾收集器回收,这样 ThreadLocal 的线程如果一直持续运行,value 就一直得不到回收,这样就会发生内存泄露。
解决办法
使用完后记得 remove
2.ThreadLocal 自己提供了这个问题的解决方案。
每次操作 set、get、remove 操作时,会相应调用 ThreadLocalMap 的三个方法,ThreadLocalMap 的三个方法在每次被调用时 都会直接或间接调用一个 expungeStaleEntry() 方法,这个方法会将 key 为 null 的 Entry 删除,从而避免内存泄漏。
使用 static 修饰 ThreadLocal
还可以使用 static 修饰 ThreadLocal,保证 ThreadLocal 为强引用,也就能保证任何时候都能通过 ThreadLocal 的弱引用访问到 Entry 的 value 值,进而清除掉 。
原因:根据可达性算法分析,类静态属性引用的对象可作为 GC Roots 根节点,即保证了 ThreadLocal 为不可回收对象
今日份分享已结束,请大家多多包涵和指点!
评论