threadlocal内存泄露_ThreadLocal 理解不当致使日志输出错误!

记一次问题排除:

系统需求:需要记录每个接口的访问记录;

日志记录逻辑:

  1. 获取请求的 url,记录日志;
  2. 校验是否登陆,未登录跳转登陆页面,登陆后将信息存到 ThreadLocal 中;

问题描述:

再查看日志时,发现一些接口访问记录,操作者并没有操作,但是却有一条访问记录; 前端童鞋检查了,发现页面并没有发送相关的请求。但是日志确实记录了访问记录;问题定位在 打印日志的地方;

30ae086a1bf9bcd51b7195926255132d.png

我就开始了研究问题原因

经过debug 发现 Logback 组件中使用了 ThreadLocal, 恰恰是同事将登陆信息存到了这里,就出现问题了;

发送到服务器的请求并不是每个请求都新建一个线程,而是使用的线程池,线程使用后会归还到线程池中;

这个时候线程并不会被销毁,所以 ThredLocal 中的值还存在,并不会删除,下一个访问使用这个线程的用户,依然引用折这些信息;

到这了我就去找隔壁的同事了

76534d57efb7e59dd4bd165e2efd5ab1.png

解决问题:

修改后的逻辑:先校验 seesion,保存 ThreadLocal 后,再打印访问记录;

ThreadLocal 的核心机制:

  • 每个 Thread 线程内部都有一个 Map。
  • Map 里面存储线程本地对象(key)和线程的变量副本(value) 但是,Thread 内部的 Map 是由 ThreadLocal 维护的,由 ThreadLocal 负责向 map 获取和设置线程的变量值。
  • Map 中以 Entry 数组形式储存,key 就是 ThreadLocal; 所以每个线程可以存储多个值,但是同一个 ThreadLocal 只能保存一份;

4f11b0210188fc66582aceb00f92a7a9.png
ThreadLocal 内存引用

ThreadLocalMap

ThreadLocalMap 在内存中的引用关系

d11b2747c1b497e302d5b3615b16474b.png
ThreaLocal 引用关系

Hash 冲突怎么解决

采用线性探测的方式,就是根据初始 key 的 hashcode 值确定元素在 table 数组中的位置,如果发现这个位置上已经有其他 key 值的元素被占用,则利用固定的算法寻找一定步长的下个位置,依次判断,直至找到能够存放的位置。

内存泄漏问题

由于 ThreadLocalMap 的 key 是弱引用,而 Value 是强引用。这就导致了一个问题,ThreadLocal 在没有外部对象强引用时,发生 GC 时弱引用 Key 会被回收,而 Value 不会回收,如果创建 ThreadLocal 的线程一直持续运行,那么这个 Entry 对象中的 value 就有可能一直得不到回收,发生内存泄露。 所以 ThreadLocal 在 get、set、remove 方法增加了对 Key 值为 null 时移除记录,避免内存泄露;

/**
* 启发式的扫描查找一些过期的实体并清除,此方法会再添加新实体的时候被调用, 
* 或者过期的元素被清除时也会被调用.
* 如果没有过期数据,那么这个算法的时间复杂度就是O(log n)
* 如果有过期数据,那么这个算法的时间复杂度就是O(n)
*/
private boolean cleanSomeSlots(int i, int n) {
    boolean removed = false;
    Entry[] tab = table;
    int len = tab.length;
    do {
        i = nextIndex(i, len);
        Entry e = tab[i];
        if (e != null && e.get() == null) {
            n = len;
            removed = true;
            i = expungeStaleEntry(i);
            }
        } while ( (n >>>= 1) != 0);
    return removed;
}

总结

  • 每个 ThreadLocal 只能保存一个变量副本,如果想要上线一个线程能够保存多个副本以上,就需要创建多个 ThreadLocal。
  • ThreadLocal 内部的 ThreadLocalMap 键为弱引用,会有内存泄漏的风险。
  • 适用于无状态,副本变量独立后不影响业务逻辑的高并发场景。如果如果业务逻辑强依赖于副本变量,则不适合用 ThreadLocal 解决,需要另寻解决方案了。

518ccba413dfc3fa01bbef1d858a924a.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值