ThreadLocal作用和原理分析

ThreadLocal主要为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。要理解ThreadLocal需要理解下面三个问题:

  1. 每个线程的变量副本是存储在哪里的?(参考ThreadLocal的get()源码)
    每个线程都有一个threadLocals成员,引用类型是ThreadLocalMap,以ThreadLocal和ThreadLocal对象声明的变量类型作为参数。这样,我们所使用的ThreadLocal变量的实际数据,通过get函数取值的时候,就是通过取出Thread中threadLocals引用的map,然后从这个map中根据当前threadLocal作为参数,取出数据。也就是说其实不同线程取到的变量副本都是由线程本身的提供的,存储在线程本身,只是借助ThreadLocal去获取,不是存放于 ThreadLocal。
  2. 变量副本【每个线程中保存的那个map中的变量】是怎么声明和初始化的?
    当线程中的threadLocals成员是null的时候,会调用ThreadLocal.createMap(Thread t, T firstValue)创建一个map。
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);
}

同时根据函数参数设置上初始值。也就是说,当前线程的threadlocalmap是在第一次调用set的时候创建map并且设置上相应的值的。
在每个线程中,都维护了一个threadlocals对象,在没有ThreadLocal变量的时候是null的。一旦在ThreadLocal的createMap函数中初始化之后,这个threadlocals就初始化了。以后每次ThreadLocal对象想要访问变量的时候,比如set函数和get函数,都是先通过getMap(Thread t)函数,先将线程的map取出,然后再从这个在线程(Thread)中维护的map中取出数据或者存入对应数据。
3. 不同的线程局部变量,比如说声明了n个(n>=2)这样的线程局部变量threadlocal,那么在Thread中的threadlocals中是怎么存储的呢?threadlocalmap中是怎么操作的?
在ThreadLocal的set函数中,可以看到,其中的map.set(this, value)把当前的threadlocal传入到map中作为键,也就是说,在不同的线程的threadlocals变量中,都会有一个以你所声明的那个线程局部变量threadlocal作为键的key-value。假设说声明了N个这样的线程局部变量变量,那么在线程的ThreadLocalMap中就会有n个分别以你的线程局部变量作为key的键值对。

ThreadLocal为什么会内存泄漏?

ThreadLocal的实现是这样的:每个Thread 维护一个 ThreadLocalMap 映射表,这个映射表的 key 是 ThreadLocal实例本身,value 是真正需要存储的 Object。
也就是说 ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value。
值得注意的是,ThreadLocalMap 是使用 ThreadLocal 的弱引用作为 Key 的,弱引用的对象在 GC 时会被回收。

//继承了弱引用
static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
			
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
}

/**
 * 实际持有的entry数组
 * The table, resized as necessary.
 * table.length MUST always be a power of two.
 */
private Entry[] table;

ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏。

ThreadLocal如何防止内存泄漏?

每次使用完ThreadLocal,都调用它的remove()方法,清除数据。
在使用线程池的情况下,没有及时清理ThreadLocal,不仅是内存泄漏的问题,更严重的是可能导致业务逻辑出现问题。所以,使用ThreadLocal就跟加锁完要解锁一样,用完就需要清理。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值