ThreadLocal内存泄漏

大家好,我是三叔,很高兴这期又和大家见面了,一个奋斗在互联网的打工人。

笔者曾在一文理解线程安全中有提到ThreadLocal,笔者就在思考,ThreadLocal是如何知道拿到key呢,这篇博客笔者将会从底层源码介绍一下ThreadLocal是如何获取到key值的以及ThreadLocal是如何发生内存泄漏。

ThreadLocal是如何获取到key值的

每一个线程的Thread对象中都有一个ThreadLocalMap对象,这个对象存储了一组以threadLocalHashCode为键,以本地线程变量为值的k-v值对,每一个ThreadLocal对象都包含了一个独一无二的threadLocalHashCode值,使用这个值就可以在线程k-v中找到对应的线程变量。

简单来说就是,ThreadLocal是通过线程对象中的ThreadLocalMap来存储和查找key-value对的。每个ThreadLocal实例都会有一个唯一的integer类型的key值,而value则可以是任意类型的对象。在Java中,线程对象中的ThreadLocalMap是通过ThreadLocal的弱引用来关联线程本地变量的。

ThreadLocal类内部维护了一个静态变量nextHashCode,用于生成每个ThreadLocal实例对应的唯一的integer类型的key值。每次创建一个新的ThreadLocal实例时,都会将nextHashCode的值作为该ThreadLocal实例的key值,并将nextHashCode的值自增。因此,每个ThreadLocal实例的key值都是唯一的,且按照创建顺序递增。

让我们从源码来看:

public class ThreadLocal<T> {
	// ThreadLocal对象都包含了一个独一无二的threadLocalHashCode值
	private final int threadLocalHashCode = nextHashCode();
	
	// 静态变量nextHashCode,用于生成每个ThreadLocal实例对应的唯一的integer类型的key值
	private static AtomicInteger nextHashCode = new AtomicInteger();
	
	// HASH_INCREMENT 是一个常量,是 0x61c88647,用于计算 hash 值
	private static final int HASH_INCREMENT = 0x61c88647;
	
	// 返回下一个哈希码
	private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }

}

当我们使用ThreadLocal的set()方法设置一个变量时,ThreadLocal会首先获取当前线程对象的ThreadLocalMap,并将ThreadLocal实例作为key,将变量作为value,存储到该ThreadLocalMap中。

public void set(T value) {
        Thread t = Thread.currentThread();
        // 当前线程对象的ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        if (map != null)
        	// 将ThreadLocal实例作为key,将变量作为value
            map.set(this, value);
        else
            createMap(t, value);
    }
    
		ThreadLocalMap getMap(Thread t) {
		        return t.threadLocals;
		    }

当我们使用ThreadLocal的get()方法获取一个变量时,ThreadLocal会首先获取当前线程对象的ThreadLocalMap,并将ThreadLocal实例作为key,从ThreadLocalMap中获取对应的value,然后返回该value。由于ThreadLocal是与当前线程对象相关联的,因此每个线程对象都有自己ThreadLocalMap,因此每个线程中的ThreadLocal变量都是相互独立的,互不影响。

 public T get() {
        Thread t = Thread.currentThread();
        // 同set方法一样,先获取当前对象的map
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
            // @SuppressWarnings是一个用于告诉编译器忽略指定类型的警告的注解
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }


	private Entry getEntry(ThreadLocal<?> key) {
		            int i = key.threadLocalHashCode & (table.length - 1);
		            Entry e = table[i];
		            if (e != null && e.get() == key)
		                return e;
		            else
		                return getEntryAfterMiss(key, i, e);
   }

ThreadLocalMap中的key是弱引用

需要注意的是,通过源码了解到,ThreadLocalMap的Entry继承了WeakReference

ThreadLocal中使用的ThreadLocalMap类中的Entry对象会持有ThreadLocal实例的弱引用作为key,而弱引用在内存不足时会被垃圾回收器回收,如果这时ThreadLocal实例已经被回收,那么对应的Entry对象却不会被自动清除,从而导致内存泄漏。
在这里插入图片描述
因此,在使用ThreadLocal时,我们应该注意及时清理ThreadLocal实例,并在不需要的时候将其设置为null,以便垃圾回收器可以及时回收。

package com.yin.demo.service;

public class ThreadLocalExample {
    // 创建一个threadLocal对象
    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        // 设置ThreadLocal值
        threadLocal.set(2023);

        // 获取ThreadLocal值
        int value = threadLocal.get();
        System.out.println("ThreadLocal value: " + value);

        // 清除ThreadLocal值
        threadLocal.remove();

        // 将ThreadLocal实例设置为null
        threadLocal = null;
    }
}

在这里插入图片描述
在上面的示例代码中,我们首先使用ThreadLocal的set()方法设置一个值,然后使用ThreadLocal的get()方法获取该值,并打印输出。接着,我们使用ThreadLocal的remove()方法清除该值,然后将ThreadLocal实例设置为null,以便垃圾回收器可以及时回收。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我是三叔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值