【HBZ分享】ThreadLocal的用法 与 原理

ThreadLocal的API

  1. set(Object object);
  2. get();
  3. remove();

ThreadLocal的基本概念

  1. ThreadLocal是每条线程独享,在调用ThreadLocal对象的set()方法时,源码会创建一个ThreadLocalMap的集合,其中Key =this,即ThreadLocal对象本身。在set(), get(), remove()这些api被调用时,都会获取当前线程,判断下当前线程的ThreadLocalMap对象是否存在,如果不存在,则创建。存在则直接赋值,那么get()就是存在返回对应值,不存在就返回null,因为value默认值是null
源码:
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

ThreadLocal的应用场景

  1. Spring的事务模板类
  2. 获取HttpsServletRequest
  3. AOP 或 拦截器中存放一些缓存数据

为什么每个线程缓存的是ThreadLocalMap对象,而不是ThreadLocal

  1. 每个ThreadLocalMap可以存放多个ThreadLocal
  2. 每个ThreadLocal只能存放一个数据
  3. 每个线程会有一个ThreadLocalMap,但ThreadLocal在一个线程中可以有多个,如果一个不够用,就可以再创建一个

强,软,弱,需引用的定义

  1. 强引用: 只要还存在引用关系,无论如何都不会被回收
  2. 软引用: 如果内存空间足够,则不会回收;如果内存空间不足,即使存在引用关系也会被回收
  3. 弱引用: 无论内存是否充足,无论是否存在引用关系,只要进行了gc,就会被回收==(ThreadLocal采用的方式)==
  4. 虚引用: 形同虚设,基本不用

内存泄漏 与 内存溢出的区别

  1. 内存泄漏: 申请的内存,无法得到释放,就是内存泄漏
  2. 内存溢出: 申请内存是,没有足够大连续空间的内存可申请,导致OOM异常,就是内存溢出(剩余总内存可能够,但连续内存空间不足)

ThreadLocal的弱引用内存泄露问题

========会出现内存泄漏的代码

ThreadLocal<String> threadLocal1 = new ThreadLocal();
// 一定要调用set方法,因为调用set才会把ThreadLocal对象放到ThreadLocalMap中
threadLocal1.set("testThreadLocal");
threadLocal1  = null;

上面代码片段,虽然赋值null,但是ThreadLocalMap依然和new ThreadLocal建立引用关系的,虽然ThreadLocalMap的Key存的指针已经是null了,但引用关系还在,所以无法被清理,最后就是内存泄漏

========不会出现内存泄漏的代码1
ThreadLocal<String> threadLocal1 = new ThreadLocal();
// 一定要调用set方法,因为调用set才会把ThreadLocal对象放到ThreadLocalMap中
threadLocal1.set("testThreadLocal");
threadLocal1.remove();
threadLocal1  = null;

调用一次remove方法即可

========不会出现内存泄漏的代码2
ThreadLocal<String> threadLocal1 = new ThreadLocal();
// 一定要调用set方法,因为调用set才会把ThreadLocal对象放到ThreadLocalMap中
threadLocal1.set("testThreadLocal");
threadLocal1  = null;

ThreadLocal<String> threadLocal2 = new ThreadLocal();
threadLocal2 .set("testThreadLocal2");

或者在后面再创建一个ThreadLocal对象,并调用set方法也可以。
原因:因为同一个线程的2个ThreadLocal对象会存放到同一个ThreadLocalMap中。而set()方法源码的处理方式是:每set一次,都会遍历ThreadLocalMap的Entry中所有数据,当有null时,会自动帮忙清空,所以这样可以避免内存泄漏

========set方法帮忙清理的代码片段

for (Entry e = tab[i];
    e != null;
    e = tab[i = nextIndex(i, len)]) {
    ThreadLocal<?> k = e.get();

    if (k == key) {
        e.value = value;
        return;
    }
	// 这里做了null判断处理
    if (k == null) {
        replaceStaleEntry(key, value, i);
        return;
    }
}

ThreadLocal为什么弱引用还会内存泄漏?

  1. 弱引用在源码是ThreadLocalMap中的Entry继承了WeakReference<ThreadLocal<?>>, 弱引用再没有进行gc的时候,依然是存在的,是占用内存的。
  2. Entry在ThreadLocalMap中的,而ThreadLocalMap的生命周期是和当前线程一致的,所以线程不结束,ThreadLocalMap不会被回收。
  3. (强制)不要再全局创建ThreadLocal
  4. (推荐)使用完后,调用下remove()方法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值