知道ThreadLocal嘛?讲讲它的内存泄漏

ThreadLocal是什么

从名字我们就可以看到ThreadLocal叫做线程变量,
意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的
ThreadLocal为变量在每个线程中都创建了一个副本,
那么每个线程可以访问自己内部的副本变量。

从字面意思来看非常容易理解,但是从实际使用的角度来看,就没那么容易了,作为一个面试常问的点,使用场景那也是相当的丰富:

1、在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。

2、线程间数据隔离

3、Spring关于事务的处理,用于存储线程事务信息。

4、Mybatis关于分页的处理

ThreadLocal的initialValue()方法的流程图

在这里插入图片描述

代码理解下

关于initialValue()方法,是由子类重写的,当get时。没有set的时候就去获取init方法。否则获取set的值

public class Profiler {
    // 第一次get()方法调用时会进行初始化(如果set方法没有调用)
    //每个线程会调用一次
    private static final ThreadLocal<Long> TIME_THREADLOCAL = 
                            new ThreadLocal<Long>() {
		//初始化重写了返回1,第一次get()方法调用时,没有set就会返回
        protected Long initialValue() {
            return 1L;
        }
    };
    public static final void begin() {
        //TIME_THREADLOCAL.set(10L);
    }
    public static final long end() {
        return TIME_THREADLOCAL.get();
    }
    public static void main(String[] args) throws Exception {
        Profiler.begin();
        TimeUnit.SECONDS.sleep(1);
        System.out.println("get取到的值:" + Profiler.end() + " ");
    }
}

当begin方法里没有set时,get方法取到的是初始化的值。
在这里插入图片描述
当begin方法里有set方法时,get方法取到的是set的值
在这里插入图片描述

ThreadLocal深入分析

我们看下这个例子

public class ThreadLocal_learn {
    public static void main(String[] args) {
        ThreadLocal<Person> threadLocal=new ThreadLocal<>();
        Person person=new Person("张三");
        new Thread(()->{
            threadLocal.set(person);
            System.out.println("线程"+Thread.currentThread().getName()+":"+threadLocal.get().name);
        }).start();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //线程2
        new Thread(()->{
            System.out.println("线程"+Thread.currentThread().getName()+":"+threadLocal.get());
        }).start();

    }
}
class Person{
    String name;
    public Person(String name) {
        this.name = name;
    }
}

定义个全局的对象person,在第一个线程里set,在第二个线程里get,那么我们能拿到吗,看运行结果
在这里插入图片描述
可以看见线程2是拿不到线程1里set的变量值的。分析源码之前呢,先来讲个基础的

JAVA中引用类型有哪些?每种引用类型的特点?

  1. 强引用,软引用,弱引用,虚引用
  2. 强引用不会被回收,即使OOM也不会被回收
    软引用当空间不足时会回收。适合当缓存使用
    弱引用看见直接回收。ThreadLocal里防止内存泄漏
    虚引用专门用于管理堆外内存的

之所以讲上边这些,是因为下面会用到,那么我们分析下源码

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

获取当前线程,调用getMap方法

ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

t是当前线程,threadLocals 是当前前程的成员变量ThreadLocalMap
在这里插入图片描述
也就是每个线程都有这个map,键就是ThreadLocal,值就是传进来的值。

然后看map.set(this, value);
在这里插入图片描述
会发现是new了一个Entry,这就是键值对。
进入这个
在这里插入图片描述
会发现这个Entry继承了个弱引用,调用了弱引用的构造方法,key就是ThreadLocal对象,值就是传进去的值。
在这里插入图片描述
因为ThreadLocalMap是属于线程本身的所以,不同的线程是只能拿本线程的变量。所以在上面的例子里线程2是拿不到线程1的对象
在这里插入图片描述

ThreadLocal的内存泄漏

那么我们看下为什么
在这里插入图片描述
这里用到了弱引用?
还是看这个图
在这里插入图片描述
但是即使这样依然还会有内存泄漏的存在。

ThreadLocal对象被回收,key的值就是null,则会导致整个value再也无法被访问到,因此依然存在内存泄漏。所以需要把这个Entry给remove掉
在这里插入图片描述
并且在线程池的时候,用完一定要手动调用remove。因为如果不清理,用的就是原来旧的值。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值