Thred、ThreadLocal、ThreadLocalMap 的关系

文章探讨了Spring在处理事务时如何通过ThreadLocal确保每个线程使用独立的数据库连接,避免数据共享问题,并强调了ThreadLocal在跨方法参数传递和内存泄漏预防中的作用,以及使用注意事项,特别是线程池配合ThreadLocal可能导致的内存累积问题。
摘要由CSDN通过智能技术生成

场景:

        Spring在处理事务管理是,遇见方法调用操作数据库时,怎么保证使用的时同一个数据库连接对象;那么Spring时如何解决这个问题的?

        其实稍微分析一下Spring的事务管理器的代码就能发现端倪,在org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin 中,我们会发现如下代码

        上面的注释已经很清楚了说明“绑定连接到这个线程”,如何绑定的?

看来,Spring是使用一个ThreadLocal来实现“绑定连接到线程”的。

 ThreadLocal定义

原文:
        This class provides thread-local variables. These variables differ from theirnormal counterparts in that each thread that accesses one (via its get or set method) has its own,
independently initi alized copy of the variable. ThreadLocal instances are typically private stati c fields in classes that wish to associate state with a thread (e.g., a user ID or Transacti on ID).
译文:
        此类提供线程局部变量。这些变量与普通对应变量的不同之处在于, 访问一 个变量的每个线程 (通过其 get 或 set 方法)都有自己独立初始化的变量副本。 ThreadLocal 实例通常是希望将状态与线 程(例如, 用户 ID 或事务 ID )相关联 的类中的私有静态字段。
        也就是说 ThreadLocal 为每个线程都提供了变量的副本,使得每个线程在某 一时间访问到的并非 同一个对象,这样就隔离了多个线程对数据的数据共享。
        由此也可以看出 ThreadLocal 和 Synchonized 都用于解决多线程并发访问。可 是 ThreadLocal 与 synchronized 有本质的差别。 synchronized 是利用锁的机制, 使 变量或代码块在某一时该仅仅能被一 个线程问, ThreadLocal 则是副本机制。
此时不论多少线程并发访问都是线程安全的。

 ThreadLocal应用场景

1、跨方法进行参数传递

        比如 Web 容器中, 每个完整的请求周期 会由一个线程来处理。 结合 ThreadLocal 再使用 Spring 里的 IOC AOP ,就可以很好的解决我们上面 的事务的问题。只要将一个数据库连接 放入 ThreadLocal 中, 当前线程执行时只要有使用数据库连接 的地方就从 ThreadLocal 获得就行了。
        再比如,在微服务领域, 链路跟踪中的 traceId 传递也是利用了 ThreadLocal

 ThreadLocal 的使用

ThreadLocal 类接口很简单,只有 4 个方法,我们先来了解一下:
void set(Object value)
        设置当前线程的线程局部变量的值。
public Object get()
        该方法返回当前线程所对应的线程局部变量。
public void remove()
        将当前线程局部变量的值删除, 目的是为了减少内存的占用, 该方法是 JDK 5.0 新增的方法。需 要指出的是,当线程结束后,对应该线程的局部变量将自动 被垃圾回收, 所以显式调用该方法清除 线程的局部变量并不是必须的操作, 但它 可以加快内存回收的速度。
protected Object initi alValue()
        返回该线程局部变量的初始值,该方法是一个 protected 的方法, 显然是为 了让子类覆盖而设计 的。这个方法是一个延迟调用方法, 在线程第 1 次调用 get() set(Object) 时才执行,并且仅执行 1 次。ThreadLocal 中的缺省实现直接返回一 个 null

ThreadLocalMap  

        ThreadLocalMap 是一 个声明在 ThreadLocal 的静态内部类, 然后 Thread 类中有一 个这样类型成员变量,也就是 ThreadLocalMap 实例化是在 Thread 内部,
所以 getMap 是直接返回 Thread 的这个成员。
        看下 ThreadLocal 的内部类 ThreadLocalMap 源码,这里其实是个标准的 Map 实现,内部有一个元 素类型为 Entry 的数组, 用以存放线程可能需要的多个副本 变量。
可以看到有个 Entry 内部静态类,它继承了 WeakReference ,总之它记录了 两个信息, 一个是 ThreadLocal<?>类型, 一个是 Object 类型的值。 getEntry 方法 则是获取某个 ThreadLocal 对应的值, set 方法就是更新或赋值相应的 ThreadLocal 对应的值。
回顾我们的 get 方法, 其实就是拿到 每个线程独有的 ThreadLocalMap
然后再用 ThreadLocal 的当前实例,拿到 Map 中的相应的 Entry ,然后就可 以拿到相应的值返回 出去。当然, 如果 Map 为空, 还会先进行 map 的创建, 初 始化等工作。

内存泄漏的现象

         每个 Thread 维护一个 ThreadLocalMap,这个 映射表的 key ThreadLocal 实例本身, value 是真正需 要存储的 Object,也就是说 ThreadLocal 本 身并不存储值, 它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value。仔细观察 ThreadLocalMap,这个 map 是使用 ThreadLocal 的弱引用作为 Key 的,弱引用的对象在 GC 时会被回 收。

因此使用了 ThreadLocal 后,引用链如图所

图中的虚线表示弱引用。
        这样, 当把 threadlocal 变量置为 null 以后,没有任何强引用指向 threadlocal 实例,所以
threadlocal 将会被 gc 回收。这样一来, ThreadLocalMap 中就会出现 key null Entry ,就没有办 法访问这些 key null Entry value ,如果当前 线程再迟迟不结束的话,这些 key null 的 Entry 的 value 就会一直存在一条强 引用链: Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value , 而这块 value 永 远不会被访问到了,所以存在着内存泄露。
        只有当前 thread 结束以后, current thread 就不会存在栈中,强引用断开, Current Thread Map value 将全部被 GC 回收。最好的做法是不在需要使用 ThreadLocal 变量后,都调用它的 remove() 方法,清除数据。
总结
        JVM 利用设置 ThreadLocalMap Key 为弱引用,来避免内存泄露。 JVM 利用调用 remove get 、set 方法的时候,回收弱引用。 当 ThreadLocal 存储很多 Key null Entry 的时候,而不再去调用 remove get set 方法,那 么将导致内存泄漏。
        使用线程池+ ThreadLocal 时要小心, 因为这种情况下, 线程是一直在不断的 重复运行的,从而 也就造成了 value 可能造成累积的情况。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值