ThreadLocal 内存泄漏问题

22 篇文章 0 订阅

1. 认识ThreadLocal

java中提高了threadlocal,为每个线程保存其独有的变量,threadlocal使用的一个小例子是:

public class ThreadLocalTest {
    public static void main(String[] args) {
        ThreadLocal<String> threadIds = new ThreadLocal<>();

        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);

        for (int i = 0; i < 10; i++) {
            fixedThreadPool.execute(() -> {
                threadIds.set("" + Thread.currentThread().getId());
                System.out.println(Thread.currentThread().getName() + ", id:" + threadIds.get());
            });
        }
    }
}


输出结果:

Thread-0, id:12
Thread-3, id:15
Thread-2, id:14
Thread-5, id:17
Thread-1, id:13
Thread-6, id:18
Thread-4, id:16
Thread-7, id:19
Thread-9, id:21
Thread-8, id:20

可以看出,每个线程内部都保存了一个不同的threadId。

2. ThreadLocal实现原理

每个线程(Thread类的实例)都具有一个叫做threadLocalMap的变量,其是一个map类型的结构,其中每个key指向一个threadLocal实例,value存储当前线程在这个threadLocal中存储的值。举例来说,在上面的程序中我们创建了10个线程,每个线程内部的threadLocalMap都具有一个entry<threadIds, v>。因此,每个线程记录、更改threadLocal中的值实际上就是在自己的threadLocalMap中找到对应的entry,然后更新entry里面的值,从而实现线程隔离。

3. ThreadLocalMap中的key是软引用

我们知道,ThreadLocalMap中的key是对ThreadLocal的引用,那为什么搞成软引用呢,我的理解是这样的。
在这里插入图片描述
图片来自于zhihu java喵
看上图,实线代表强引用,虚线代表弱引用。
我们结合上面的代码来理解一下:
首先,我们需要知道线程的寿命是很长的,我们在线程池中创建的线程之后可能还会去运行其他的runnable对象,因此需要动态地清理其threadLocalMap中无用的entry。

  1. 当main方法被执行时,main方法被压入虚拟机栈,创建了10个线程,假设线程还没终止,那么此时threadIds对象具有1个强引用(来自于main线程的ThreadLocal threadIds = new ThreadLocal<>();这句)和10个弱引用(来自于10个线程的threadLocalMap中的key)。
  2. 当main方法运行之后,main方法被弹出栈,此时threadIds对象只具有10个弱引用(来自于10个线程的threadLocalMap中的key)。
  3. java gc线程发现threadIds对象只有弱引用,那么在gc时就会回收这个对象,之后每个线程的threadLocalMap中这个entry的key就变成了null。(此时entry是<null, v>)。
  4. 由于此时这条entry已经无用的,但是v还指向其引用的对象,那么v就无法被回收,造成了内存泄漏。
  5. 我刚看到这个问题时,想着解决办法也很简单,线程发现key是null之后,将v也置为null,等着gc回收v不久好了吗,实际上也确实可以这么做。

Q:为什么ThreadLocalMap的key是弱引用?
A:结合上面的流程就很简单了,如果key是弱引用,那么main方法都运行完了,threadIds都已经无用了,此时还有十个强引用指向它,这让gc怎么回收呢?

4. 内存泄漏解决方法

使用完threadLocal对象之后,调用remove方法,将其从线程的threadLocalMap中清理出去:

public class ThreadLocalTest {
    public static void main(String[] args) {
        ThreadLocal<String> threadIds = new ThreadLocal<>();

        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);

        for (int i = 0; i < 10; i++) {
            fixedThreadPool.execute(() -> {
                threadIds.set("" + Thread.currentThread().getId());
                System.out.println(Thread.currentThread().getName() + ", id:" + threadIds.get());
                // remove
                threadIds.remove();
            });
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

canaryW

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

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

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

打赏作者

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

抵扣说明:

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

余额充值