ThreadLocal底层原理及运用场景

1、什么是ThreadLocal

ThreadLocal并不是用来解决多线程的共享变量问题,而是提供了线程的局部变量,每个线程都可以通过set()get()来对这个局部变量进行操作,但不会和其他线程的局部变量进行冲突,即本线程共享内部变量,实现了与其它线程的数据隔离。

2、为什么要学习ThreadLocal:

  1. 最典型的是管理数据库的Connection。频繁创建和关闭Connection是一件非常耗费资源的操作,因此需要创建数据库连接池。那么,数据库连接池交由ThreadLocal来进行管理。ThreadLocal能够实现当前线程的操作都是用同一个Connection,保证了事务!
  2. 避免一些参数传递:以Cookie和Session为例:
  • 每当我访问一个页面的时候,浏览器都会帮我们从硬盘中找到对应的Cookie发送过去。
  • 浏览器是十分聪明的,不会发送别的网站的Cookie过去,只带当前网站发布过来的Cookie过去。
  • 浏览器就相当于我们的ThreadLocal,它仅仅会发送我们当前浏览器存在的Cookie(ThreadLocal的局部变量),不同的浏览器对Cookie是隔离的(Chrome,Opera,IE的Cookie是隔离的【在Chrome登陆了,在IE你也得重新登陆】),同样地:线程之间ThreadLocal变量也是隔离的。

3、ThreadLocal底层实现原理:

3.1 ThreadLocal的内部类

TheadLocal的结构比较简单,只有三个内部类,具体情况如截图所示:

ThreadLocal底层原理及运用场景

其数据结构是每个线程Thread类都有个属性ThreadLocalMap,用来维护该线程的多个ThreadLocal变量,该Map是自定义实现的Entry[]数组结构,并非继承自原生Map类,Entry其中Key即是ThreadLocal变量本身,Value则是具体该线程中的变量副本值。结构如图:

ThreadLocal底层原理及运用场景

因此ThreadLocal其实只是个符号意义,本身不存储变量,仅仅是用来索引各个线程中的变量副本。

值得注意的是,Entry的Key即ThreadLocal对象是采用弱引用引入的。

3.2 弱引用

java语言中为对象的引用分为了四个级别,分别为 强引用 、软引用、弱引用、虚引用。

弱引用具体指的是java.lang.ref.WeakReference类。

对对象进行弱引用不会影响垃圾回收器回收该对象,即如果一个对象只有弱引用存在了,则下次GC将会回收掉该对象(不管当前内存空间足够与否)。

再来说说内存泄漏,假如一个短生命周期的对象被一个长生命周期对象长期持有引用,将会导致该短生命周期对象使用完之后得不到释放,从而导致内存泄漏。

因此,弱引用的作用就体现出来了,可以使用弱引用来引用短生命周期对象,这样不会对垃圾回收器回收它造成影响,从而防止内存泄漏。

3.3 ThreadLocal中的弱引用

1.为什么ThreadLocalMap使用弱引用存储ThreadLocal?

假如使用强引用,当ThreadLocal不再使用需要回收时,发现某个线程中ThreadLocalMap存在该ThreadLocal的强引用,无法回收,造成内存泄漏。

因此,使用弱引用可以防止长期存在的线程(通常使用了线程池)导致ThreadLocal无法回收造成内存泄漏

2.那通常说的ThreadLocal内存泄漏是如何引起的呢?

我们注意到Entry对象中,虽然Key(ThreadLocal)是通过弱引用引入的,但是value即变量值本身是通过强引用引入。

这就导致,假如不作任何处理,由于ThreadLocalMap和线程Thread的生命周期是一致的,当线程资源长期不释放,即使ThreadLocal本身由于弱引用机制已经回收掉了,但value还是驻留在线程的ThreadLocalMap的Entry中。即存在key为null,但value却有值的无效Entry。导致内存泄漏。

如图所示大致解释了内存泄漏 ThreadLocalMap和线程Thread的生命周期是一致的。

ThreadLocal底层原理及运用场景 

4、怎么解决内存泄漏带来的问题

  1. 强制)在代码逻辑中使用完ThreadLocal,都要调用remove方法,及时清理。一方面是防止内存泄漏,最为重要的是,不及时清除有可能导致严重的业务逻辑问题,产生线上故障(使用了上次未清除的值)。
  2. ThreadLocal一般加static修饰,同时要遵循及时清理调用remove方法一方面ThreadLocal不加static,则每次其所在类实例化时,都会有重复ThreadLocal创建。这样即使线程在访问时不出现错误也有资源浪费。另一方面在某些代码规范中遇到过这样一条要求:“尽量不要使用全局的ThreadLocal”实际上,这个解读都是不必要的,首先,静态ThreadLocal资源回收的问题,即使ThreadLocal本身无法回收,但线程中的Entry是可以通过remove清理掉的也就不会出现泄漏。第二种解读,多次复用值改变的问题,其实在调用remove后也不会出现。

本文参考博文:https://mp.weixin.qq.com/s?__biz=MzI4Njg5MDA5NA==&mid=2247484118&idx=1&sn=da3e4c4cfd0642687c5d7bcef543fe5b&chksm=ebd743d7dca0cac19a82c7b29b5b22c4b902e9e53bd785d066b625b4272af2a6598a0cc0f38e&scene=21###wechat_redirect

https://zhuanlan.zhihu.com/p/91579723