ThreadLocal的原理和内存泄漏问题分析

10 篇文章 0 订阅
3 篇文章 0 订阅

ThreadLocal的原理和内存泄漏问题分析

ThreadLocal概述

ThreadLocal意思是线程本地类,该变量对其他变量是隔离的

ThreadLocal的使用场景

当某些变量只希望自己的线程获取,变量值只需要在当前线程中存取时,就可以考虑使用ThreadLocal

  • 存储用户信息:使用Token登陆时,可以使用Interceptor拦截器,请求达到前,先根据Token判断用户是否登录,同时就可以把用户信息存储到ThreadLocal中,进入业务后,通过ThreadLocal来取,每个用户都有自己的线程,所以用户只能取出来自己的用户信息。

ThreadLocal原理分析

原来的设计:
对每个ThreadLocal开辟一个Map,线程要存储数据时,可以使用Thread的id当key,数据为value,这样就可以分离不同的线程。

新版的设计:

  1. 每个线程都有自己的ThreadLocalMap对象,ThreadLocal对象为key,线程变量副本为value
  2. ThreadLocal用来维护每个线程的ThreadLocalMap对象
/* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. 这是Thread的源码,默认为NUll*/
    ThreadLocal.ThreadLocalMap threadLocals = null;
  1. ThreadLocalMap是ThreadLocal的静态内部类

版本的版本:

  1. 每个Entry存储的Entry数量变少,本来是每个线程都需要一个Entry存储,尽量避免了哈希冲突的发生
  2. 当线程销毁后,内部的ThreadLocalMap也会消失,而以Thread id为key的还需要手动删除

ThreadLocal源码分析

set()
设置value到ThreadLocalMap中,key就是这个ThreadLocal对象

    public void set(T value) {
    	// 获取当前线程
        Thread t = Thread.currentThread();
        // 获取线程内的ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        if (map != null)
        	// 已经有map,这是k-v是 ThreadLocal-设置的数据
            map.set(this, value);
        else
        	// 变量默认是null,这里初始化
            createMap(t, value);
    }

get()

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
        	// 直接从对应的Thread中获取ThreadLocalMap的Entry,key就是this,value就是要的值
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        // 如果不存在数据就返回初始值,初始值就是null,初始值可以被子类重写
        return setInitialValue();
    }

remove()
就拿出来ThreadLocalMap后删除this(ThreadLocal对象)

     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

ThreaLocalMap源码分析

总体来说ThreadLocal的代码不难,ThreadLocal的难点在ThreadLocalMap这个静态内部类

类定义,并没有继承或实现Map接口

	// 并没有实现Map接口,而是自己实现了一个
    static class ThreadLocalMap {
    	// 内部的Entry也是自己实现的并且继承了弱引用
        static class Entry extends WeakReference<ThreadLocal<?>> {
        	// 构造方法super(k) 来让ThreadLocal对象被WeakReference封装
        	Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        	.....
        }
        ......
   }

成员变量,类似于HashMap

		// 初始容量 
        private static final int INITIAL_CAPACITY = 16;
        // Entry数组,长度必须是2的幂次,懒加载
        private Entry[] table;
        // 实际的数量
        private int size = 0;
        // 扩容的门槛
        private int threshold;

Entry为什么继承弱应用

WeakReference修饰的类当GC时就会被回收,目的是将ThreadLocal的生命周期和线程的生命周期分离

Entry中key如果强引用会造成内存泄漏么?

很多人对此有误解
手画图示
请添加图片描述

如果只有一个线程,ThreadLocal不使用之后,堆中的ThreadLocal也无法释放,因为TheadLocalMap中一个Entry的key是它的强引用,这时候就无法访问到这个Entry,造成了内存泄漏(虽然可以通过ThreadLocalMap通过地址来访问,但这个地址无意义,除非你提前存储了它,所以这种内存泄漏我认为是类似内存泄漏),所以强弱引用和内存泄漏无关,主要是这种设计造成的内存泄漏

只要线程不结束,ThreadLocalMap无法回收,强引用指向ThreadLocal,也不会被回收

源码中,key为弱引用

如果是弱引用,ThreadLocal的引用消失后,就只剩下ThreadLocalMap中的key的弱引用指向ThreadLocal,当触发GC时,该引用就会被回收掉,这时ThreadLocal不在GC ROOT,也就会被回收,此时的Entry的key为null,value还存在,只要线程还存在,就不会释放Map,这个key为null,value为数据的Entry永远无法被访问。这就导致了内存泄漏

内存泄漏总结

导致内存泄漏的主要原因是->ThreadLocalMap的生命周期和Thread一样长

避免内存泄漏的方式:

  • 使用完后立即remove()
  • 使用完后线程也结束了
    都会导致内存泄漏,为什么使用弱引用呢?
    GC发生时,内存泄漏位置key = null,ThreadLocalMap在执行set/get方法时,会对key为null进行判断,key为null,就会把value也设置为null,比强引用多了一层保障

ThreadLocalMap的哈希冲突解决

不做解释了,使用的是线性探测法解决的哈希冲突,相当于把Map当成环形数组,一直向右探测

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

踢足球的程序员·

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

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

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

打赏作者

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

抵扣说明:

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

余额充值