ThreadLocalMap原理源码级讲解

本文详细解析了Java中的ThreadLocal在1.7和1.8版本的区别,强调1.8版本的优化点,包括减少遍历时间和避免哈希冲突。同时,解释了ThreadLocal的set和get方法的工作原理,并介绍了ThreadLocalMap的内部结构。文章还讨论了ThreadLocal可能导致的内存泄漏问题及其解决方案,建议在使用完毕后调用remove方法以防止内存泄漏。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.7版本和1.8版本是不一样的。

1.7版本:

ThreadLocal里面维护一个TheradLocalMap,底层是Map,再底层是entry对象,(key,value)形式,key是Thread,value是需要隔离的变量。

1.8版本 :

每个Thread都维护了一个ThreadLocalMap,底层是Map,再底层是entry对象,entry中存储ThreadLocal是key,value是需要隔离的变量。

1.8版本的好处 :

1 .每个Map存储的Entry数量很少,因为1.7版本,每个线程就会代表一个entry,线程越多,entry越多,遍历耗时。1.8版本改成了entry中存储ThreadLocal和value,一般开发过程中,ThreadLocal数量是小于Thread的,所以时间上有优势。因为key少了,也可以避免哈希冲突。hashMap的哈希冲突是拉链法,而ThreadLocalMap的hash冲突是线性探索法。

2 .当Thread销毁的时候,ThreadLocalMap也会随之销毁,减少内存的使用。因为ThreadLocalMap是在Thread里面的,所以只要Thread消失了,那map就不复存在了。

ThreadLocal的set方法原理 :

public void set(T value) {
//获取当前线程对象
    Thread t = Thread.currentThread();
//获取此线程对象中维护的ThreadLocalMap对象
    ThreadLocalMap map = getMap(t);
    if (map != null)
 //map存在则调用set方法,set设置此实体entry
        map.set(this, value);
    else
  //  1.当前Thread线程不存在ThreadLocalMap对象
  //  2. 则调用createMap进行TheradLcoalMap对象的初始化
  //  3. 并将t(当前线程)和value(t对应的值)作为第一个entry存放到TheradLocalMap中
        createMap(t, value);
}
void createMap(Thread t, T firstValue) {
 //这里的this是调用此方法的ThreadLocal
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

ThreadLocal的get原理:

public T get() {
//获取当前线程对象
    Thread t = Thread.currentThread();
//获取此线程对象中维护的ThreadLocalMap对象
    ThreadLocalMap map = getMap(t);
    if (map != null) {
//如果此map存在,
        ThreadLocalMap.Entry e = map.getEntry(this);
//以当前的ThreadLocal为key,调用getEntry获取对应的存储实体e
        if (e != null) {
            @SuppressWarnings("unchecked")
//获取存储实体e对应的value值
//就是我们想要的当前线程对应此ThreadLocal的值。
            T result = (T)e.value;
            return result;
        }
    }
//初始化:
   第一种情况: map不存在,表示此线程没有维护的TheradLocalMap对象
   第二种情况: map存在,但是没有与当前ThreadLocal关联的entry。
    return setInitialValue();
}
private T setInitialValue() {
   //调用initialvalue方法获取初始化的值
   //则会个方法可以被重写,如果不重写默认返回null;
   T value = initialValue();
   //获取当前线程对象
   Thread t = Thread.currentThread();
   //获取此线程对象中维护的ThreadLocalMap对象
   ThreadLocal.ThreadLocalMap map = getMap(t);
   if (map != null)
      //存在则调用map.set设置此实体entry
      map.set(this, value);
   else
      // 1。当前线程Thread不存在ThreadLocalMap对象
       // 2。则调用creatMap进行TheradLocalMap对象的初始化
       // 3. 并将t(当前线程)和value(t对应的值)作为第一个entry存放至ThreadLocalMap中
      createMap(t, value);
   //返回设置的值
   return value;
}

总结 :先获取当前线程的ThreadLocalMap变量,如果存在则返回值,不存在则创建并返回初始值。

ThreadLocalMap的基本结构:

 ThreadLocalMap的ThreadLocal的内部类,没有实现map接口,用独立的方式实现了Map的功能,其内部的Entry也是独立实现的。

成员变量:

   //初始容量,必须是2的整次幂
   private static final int INITIAL_CAPACITY = 16;
   //存放数据的Table,长度也必须是2的整次幂
   private ThreadLocal.ThreadLocalMap.Entry[] table;
   //数组里面entrys的个数,可以用于判断table当前使用量是否超过阈值
   private int size = 0;
   //进行扩容的阈值,表使用量大于它的时候进行扩容。
   private int threshold; // Default to 0

存储结构 -Entry

 如果key为null(entry.get()==null)意味着key不再被引用,此时entry就可以从table中清除

static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

在ThreadLocalMap中,用Entry保存key-v结构。不过Entry中的key,只能是ThreadLocal对象,写死的。而且Entry继承自WeakReference,也就是说key是弱引用,只能活到下次GC开始,目的是将ThreadLocal对象的生命周期和线程生命周期进行解绑。

内存泄露问题:

假设是key是强引用,那么当ThreadLocal Ref断开后,依然会有Entry中的key进行引用,只要当前CurrentThreadRef依然存在,那么ThreadLocal就存在强引用,无法被释放。

图下为弱引用,value无法被回收,会导致内存泄漏。

内存泄漏真正的原因:

1.没有手动删除Entry

2.CurrentThread依然在运行。

解决 :

 1.ThreadLocal被调用完之后调用其remove方法,把Entry删了,就可以避免内存泄漏。

2.当前线程运行结束后,就结束掉,而不是继续阻塞或者等待运行其他。因为ThreadLocalMThreadap是Thread的一个属性,所以它的生命周期和线程一样长。

扩展问题:

key为什么要用弱引用?

 假设用完了没有调用remove方法,也就是说此时key为null,但是value有值,并且无法访问。那么在ThreadLocalMap的set/getEntry方法中,会对key为null,进行判空,如果为null,会把value也设置为null。也就是说,在下一次ThreadLocalMap调用的时候,会把上一次遗留的value清除掉。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值