难倒无数程序员的ThreadLocal原理,就这样被美团大牛轻松讲透彻

200 阅读4分钟

今日分享开始啦,请大家多多指教~

什么是ThreadLocal?

ThreadLocal 是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰。

ThreadLocal怎么使用?

ThreadLocl使用比较简单,主要有三个方法:get()、set()、remove()

相信不用我多说各位小伙伴也知道是什么意思

image.png

image.png

ThreadLocal底层原理

点开 ThreadLocal 的 set()方法

  • 首先是获取到当前线程

  • 调用getMap(t)方法获取到 ThreadLocalMap

  • 如果map不为null则进行set操作,如果为null则进行创建map操作

image.png

点开get()方法

跟HashMap差不多,小伙伴们自己看看源码绝对能懂

image.png

点开remove()方法

跟HashMap差不多,找到索引,遍历又对因key就删除

image.png

细心的小伙伴可能就发现了这三个方法中都需要去获取:当前Thread和ThreadLocalMap

那么这是为什么呢???

我们接着看源码,点开getMap()方法

可以发现,ThreadLocalMap获取的是Thread中的一个对象

相信到这大家就明白了 ThreadLocal是怎么做到各个线程互不干扰的吧。

因为获取到的是当前线程的ThreadLocalMap,各个线程所以互不干扰。

ThreadLocalMap getMap(Thread t) {

return t.threadLocals; }

image.png

小伙伴们呢可别觉得已经掌握了,还没完呢,这才哪到哪,我们接着看

打开ThreadLocalMap的set方法

image.png

image.png

看过上面源码,熟悉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的一个经典面试题,后面会进行详细讲解,小伙伴们要有耐心继续看哦!!!

image.png

到此基本结束,不要奇怪,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 值占用,则利用固定的算法寻找一定步长的下个位置,依次判断,直至找到能够存放的位置。

image.png

使用CAS,每次增加固定的值,所以采用的是线性探测法解决HasH冲突。

image.png

经典CAS

image.png

ThreadLocal内存泄漏问题及解决办法

ThreadLocal 在 ThreadLocalMap 中是以一个弱引用身份被 Entry 中的 Key 引用的,因此如果 ThreadLocal 没有外部强引用来引用它,那么 ThreadLocal 会在下次 JVM 垃圾收集时被回收。这个时候 Entry 中的 key 已经被回收,但是 value 又是一强引用不会被垃圾收集器回收,这样 ThreadLocal 的线程如果一直持续运行,value 就一直得不到回收,这样就会发生内存泄露。

image.png

解决办法

  1. 使用完后记得remove

2.ThreadLocal自己提供了这个问题的解决方案。

每次操作set、get、remove操作时,会相应调用 ThreadLocalMap 的三个方法,ThreadLocalMap的三个方法在每次被调用时 都会直接或间接调用一个 expungeStaleEntry() 方法,这个方法会将key为null的 Entry 删除,从而避免内存泄漏。

image.png

  1. 使用static修饰ThreadLocal

还可以使用 static 修饰ThreadLocal,保证ThreadLocal为强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉 。

原因:根据可达性算法分析,类静态属性引用的对象可作为GC Roots根节点,即保证了ThreadLocal为不可回收对象

今日份分享已结束,请大家多多包涵和指点!