今日分享开始啦,请大家多多指教~
什么是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 getMap(Thread t) {
return t.threadLocals; }
小伙伴们呢可别觉得已经掌握了,还没完呢,这才哪到哪,我们接着看
打开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为不可回收对象
今日份分享已结束,请大家多多包涵和指点!