谈谈你对ThreadLocal以及内存泄漏的理解

引入

 ThreadLocal<Object> tl = new ThreadLocal<>();
 tl.set(new Object());

在工作中,我们通常会这么使用ThreadLocal,那么这个ThreadLocal到底是什么呢?为什么会能够起到线程隔离的作用?内存泄漏又是什么呢

基础概念

ThreadLocal:线程本地变量,通过set和get方法来存储和获取当前线程封装的唯一的数据结构

java四大引用类型

  • 强引用
         使用=来表示强引用,例如Object obj = new Object(), 这里的obj就是一个强引用,指向堆空间中开辟的一片Object区域
         特点:jvm宁愿抛OOM也不会回收强引用指向的对象

  • 弱引用
         使用WeakReference关键字定义的引用
         特点:如果这个对象只存在弱引用,那么在下一次垃圾回收的时候必然会被清理掉

  • 软引用
         使用SoftReference关键字定义的引用
         特点:只有堆内存空间不够用的时候才会被回收

  • 虚引用
         使用PhantomReference关键字定义的引用
         特点:跟踪对象被垃圾回收的状态,仅仅是提供了一种确保对象被finalize以后,做某些事情的机制

ThreadLocal、ThreadLocalMap、Entry、Thread之间的关系

了解完以上内容后,我们来梳理一下三者的一个关系:

如下图,ThreadLocalMap是定义在ThreadLocal 的内部类,在ThreadLocalMap中又定义了Entry,这个Entry的键的类型为ThreadLocal类型,并且是一个弱引用

在这里插入图片描述

ThreadLocal源码

在这里插入图片描述

我们结合上图看ThreadLocal类的set方法的源码

public void set(T value) {
     Thread t = Thread.currentThread();
     ThreadLocalMap map = getMap(t);
     if (map != null)
         map.set(this, value);
     else
         createMap(t, value);
}

主要包含这几个步骤

1.获取当前执行中的线程
2.获取线程中的ThreadLocalMap对象
3.第一次获取会new一个新的ThreadLocalMap,并set值
4.如果创建过了,就直接往map中set值

get方法

public T get() {
    Thread t =Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

setInitialValue方法

private T setInitialValue() {
     T value = initialValue();
     Thread t = Thread.currentThread();
     ThreadLocalMap map = getMap(t);
     if (map != null)
         map.set(this, value);
     else
         createMap(t, value);
     return value;
}

我们都知道在Java中成员变量都会有默认值,而ThreadLocal调用get时也会有默认值,在必要的情况下,我们可以通过重写initialValue方法设定ThreadLocal.get返回变量的初始值。默认情况下initialValue返回的是null。

get方法主要包含这几个步骤

1.获取当前执行中的线程
2.获取线程中的ThreadLocalMap对象
3.如果之前这个线程set过值,就会取出对应的value
4.如果之前这个线程没有set过值,就先set一个null,再返回null

remove方法

public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);
}

将当前线程局部变量的值删除,目的是为了减少内存的占用。该方法是JDK5.0 新增的方法。
当线程结束后,对应该线程的局部变量将自动被垃圾回收,因此调用该方法清除线程的局部变量并不是必须的操作,但调用它可以使内存回收更加及时,加速内存的回收。

为什么使用弱引用

我们知道,ThreadLocalMap中的Entry结构的Key用到了弱引用

而当ThreadLocalRef被回收时,强引用消失,此时ThreadLocal只有一个弱引用指向,因此Heap中的ThreadLocal会被回收,这就是为什么key为什么使用弱引用的原因
在这里插入图片描述

为什么ThreadLocal能够起到线程隔离的作用呢

其实ThreadLocal本身不存放任何的数据,而ThreadLocal中的数据实际上是存放在线程实例中,ThreadLocal存入值时使用当前ThreadLocal实例作为key,存入当前线程对象中的Map中去,因此不同线程调用ThreadLocal.get时,都是从各自的ThreadLocalMap中获取。
在这里插入图片描述

内存泄漏的原因

当ThreadLocal被回收后,ThreadLocalMap中对应的Key就会指向null,而对应Value却不为null,这些value项如果不主动清理,就会一直驻留在ThreadLocalMap中。
在这里插入图片描述

如何避免内存泄漏

  1. 每次使用完ThreadLocal后都使用 try-finally 块调用remove方法确保清空value
  2. ThreadLocal变量设置为static final 共用一个,保证强引用
  3. ThreadLocal内部的优化,会在我们调用set方法后,进行全量清理,清理出key为null的值,扩容也会继续检查
  4. 调用get方法后,没有直接命中或向后环形清除,会进行清理
  5. 调用remove方法的时候,会向后进行环形清理

3、4 、5三种方式是ThreadLocal内部为我们做一些措施,但是这些措施并不能完全帮助我们完全避免保证内存泄漏,这是因为

  • 使用线程池的时候,这个线程执行任务结束,ThreadLocal对象被回收了,线程放回线程池中不销毁,这个线程一直不被使用,导致内存泄漏。
  • 分配使用了ThreadLocal又不再调用get(),set(),remove()方法,那么这个期间就会发生内存泄漏。

因此这里我们建议大家使用1+2结合的方式

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
ThreadLocalJava中的一个类,用于实现线程本地变量。它的作用是为每个线程提供一个独立的变量副本,使得每个线程都可以独立地操作自己的变量副本,而不会影响其他线程的副本。\[1\] ThreadLocal的实现原理是通过在每个线程中维护一个ThreadLocalMap对象来存储变量副本。每个ThreadLocal对象作为key,对应的变量副本作为value,存储在当前线程的ThreadLocalMap中。这样,不同线程之间的变量副本是相互隔离的,每个线程只能访问自己的变量副本。\[2\] 当我们使用ThreadLocal的set方法设置变量值时,实际上是将值存储在当前线程的ThreadLocalMap中,而使用get方法获取变量值时,会先获取当前线程对象,然后使用这个线程对象去访问ThreadLocalMap中的数据,从而获取到对应的变量副本。\[2\] ThreadLocal的使用场景包括但不限于以下几种情况: 1. 在多线程环境下,需要为每个线程维护独立的变量副本,避免线程安全问题。 2. 在某些情况下,需要将一些数据在方法调用链中传递,而不希望在每个方法中都显式传递参数。 3. 在Web应用中,可以将一些需要在同一请求中共享的数据存储在ThreadLocal中,避免使用全局变量或者在方法间传递参数的方式。 需要注意的是,使用ThreadLocal时要注意内存泄漏的问题。由于ThreadLocalMap中的Entry对象是使用ThreadLocal作为key的弱引用,如果ThreadLocal没有被外部引用,那么在垃圾回收时,ThreadLocal可能会被回收,但是对应的变量副本却无法被回收,从而导致内存泄漏。因此,在使用完ThreadLocal后,应该及时调用remove方法将其从ThreadLocalMap中移除。\[3\] #### 引用[.reference_title] - *1* *2* *3* [【Java面试】谈一谈你对ThreadLocal理解](https://blog.csdn.net/Zhangsama1/article/details/128215901)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值