Java 并发之ThreadLocal和内存泄漏

ThreadLocal的作用

是一个线程内部的存储类

  • 本质上,ThreadLocal是通过空间来换取时间,从而实现每个线程当中都会有一个变量的副本,这样每个线程就都会操作该副本,从而完全规避了多线程的并发问题。

基本使用

  • 代码
public class ThreadLocalT {
    public static void main(String[] args) {
        ThreadLocal<String> threadLocal = new ThreadLocal<>();//维护的变量类型是String
        threadLocal.set("hello world");//set值
        System.out.println(threadLocal.get());//获取值
        threadLocal.set("Kitty Guy");//set值,会覆盖之前值
        System.out.println(threadLocal.get());//获取的是最新set的值
    }
}
复制代码

image.png

大致了解ThreadLocal原理

构造方法

image.png

先看get方法

image.png

  • 里面的getMap方法

image.png

  • 里面ThreadLocalMap

image.png

实际上是ThreadLocal的静态内部类ThreadLocalMap为每个Thread都维护了一个数组table,ThreadLocal确定了一个数组下标,而这个下标就是value存储的对应位置。

再看set方法

image.png

关于引用

  • Java中存在四种类型的引用:
  1. 强引用(strong):不会被垃圾回收。
  2. 软引用(soft):内存不够的情况下,会被垃圾回收。
  3. 弱引用(weak):下一次垃圾回收时,会被回收。前提是没有被强引用再去指向它。
  4. 虚引用(phantom):不指向对象,当对象被回收时,会收到通知,进行后续操作处理。

它们有垃圾回收时机上的区别,除了强引用,其他引用都要继承Reference<T>

内存泄漏

ThreadLocal在的JVM内存情况

image.png

关键点就Entry数组下标(K)是ThreadLocal的引用

image.png

从两个角度分析内存泄漏

先假设K的引用是强引用

  • 当程序运行到某一时刻,需要销毁ThreadLocal对象,它先会销毁栈中的ThreadLoacl的引用。
  • 由于堆中的Entry数组的下标K是ThreadLocal的强引用,这就导致虽然栈中的引用销毁了,但是堆中的没有被销毁,且无法销毁。这就进一步导致了。
  • Entry数组里的内容无法清除,如果重复多次使用同一个Entry数组,就导致上述情况累积发生,然后数组爆满,造成内存泄漏。
  • 即 K和V 存进去就无法清除,累积后数组爆满,内存泄漏。

image.png

既然强引用不好用,就用其它引用

实际情况K的引用是弱引用

单独一个弱引用,也无法解决内存泄漏

  • 当程序运行到某一时刻,需要销毁ThreadLocal对象,它先会销毁栈中的ThreadLoacl的引用。
  • 栈中的ThreadLocal强引用被销毁,由于堆中的Entry数组的下标K是ThreadLocal的弱引用,在下一次垃圾会回收时,因堆中的ThreadLocal没有被强引用,遂会被销毁。至此
  • K无法被消除的情况解决了。但是
  • V却没有删除,就导致了Entry中空有V却没有对应的数组下标与其对应,如此下去,就会导致数组会有很多的V没有对应的K,V的数量增加下去,导致Entry数组爆满,内存泄漏。

image.png

弱引用没有彻底解决内存泄漏咋整?

在代码层面进行一番操作即可

  • jdk源代码

在调用ThreadLocal的get、set等方法时,都会对Entry数组没有K的Value进行清除,因为弱引用会清除Entry数组的K,但不会清除的Value,只要清除了Value就解决了内存泄漏。

image.png

在ThreadLocal的实际应用中应该使用下面的形状,来防止内存泄漏

public class ThreadLocalT {
    private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();//维护的变量类型是String
        try{
            ......
            ......
    }finally {
        threadLocal.remove();//很关键,防止内存泄漏
    }
}
复制代码
  • 因为弱引用需要移除强引用的基础上,才能被垃圾回收
  • 调用remove方法就是移除栈中的强引用。才能确保不会发生内存泄漏。

小结

  • ThreadLocal不是真正存储变量的,ThreadLocalMap中Entry数组才是。
  • ThreadLocalMap是Thread类中的成员变量。
  • 调用ThreadLocal的get、set和remove方法会清除Entry数组中没有K的Value。
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享