ThreadLocal源码分析,线程局部变量,内存泄漏?

ThreadLocal作为线程局部变量,线程级的,单个线程内共享的;一般来说可以有两方面的用途:
①. 作为共享变量
②. 在某些方法计算的结果,要共享到其他方法。

在使用时,通过threadLocal.set()设置值,通过threadLocal.get()获取值,那么ThreadLocal实现单个线程间共享变量是如何实现的呢?都说ThradLocal使用不当会造成内存泄露问题,究竟是怎样的?下面将从源码角度分析…

先来看看ThreadLocal的设计结构(图1-1):也就是说,Thread类中定义了一个ThreadLocalMap,这个ThreadLocalMapThreadLocal的内部类,而ThreadLocalMap中是一个Entry数组,Entry数组中是一个个以ThreadLocal为键,用户存入的数据为值的结构;并且Entry是继承WeakReference弱引用的(图1-2)。(当一个线程是存活状态时,并且ThreadLocal实例是可访问的,每个线程都会隐式地持有一个线程局部变量副本的引用;在一个线程死亡后,它的所有线程本地实例副本都要接受垃圾收集,除非存在对这些副本的其他引用)
在这里插入图片描述
(图1-1)
在这里插入图片描述
(图1-2)

初步了解了它的结构,那么接下来看看它提供的方法。

thradLocal.set()方法用于设置数据(图1-3),跟踪源码可以看到,首先是获取当前线程,然后从当前线程中去拿取ThreadLocalMap,(可以看到,ThreadLocalMap是定义在Thraed类中的,但却是ThreadLocal的内部类)
⑴.如果有,直接设置值:根据当前threadLocalthradLocalMap中遍历查找;可以看到,先用key,也就是threadLocal,通过里面的hashCode计算下标,在Entry数组中从下标开始进行匹配;如果匹配到了,是为重复存值,直接覆盖原值;如果knull,替换已经过期的key;如果没有匹配到,则创建一个Entry加入数组中。
⑵. 如果没有,创建一个ThreadLocalMap,并且创建一个Entry作为第一个值。
在这里插入图片描述
(图1-3)

threadLocal.get()方法用于获取数据(图1-4),可以看到,先获取当前线程,然后从当前线程中获取ThreadLocalMap
⑴. 如果ThreadLocalMap存在,则从ThreadLocalMap中获取值返回;(先根据当前threadLocal计算hashCode作为Entry数组下标,如果存在则返回;不存在则进行更细致的查找:在Entry数组中从下标之后的位置开始匹配,匹配到了则返回;匹配到为null,已过期则清除过期数据;没有则返回空)
⑵. 如果ThreadLocalMap不存在,那就设置初始化值。(进行创建ThradLocalMap,然后创建entry将当前threadLocal为键,null为值,存入threadLocalMap中)。
在这里插入图片描述
(图1-4)

threadLocal.remove()方法用于移除数据(图1-5),先获取ThreadLocalMap,如果存在的或则进行移除操作。同样也是先根据当前thradLocal根据hashCode计算数组下标进行匹配,匹配到了则进行clear清除操作,将referent设置为null,也就是将key设置为null。因为key是弱引用的,当下次进行GC操作时,会被垃圾回收。

在这里插入图片描述
(图1-5)

最后,再来说说ThreadLocal内存泄漏的问题,刚刚remove方法也看到了,会将key设置为null,然后等待垃圾回收机制进行回收。那么弱引用一定会被垃圾回收吗?
代码证明:定义了一个People类继承弱引用,里面是Man类,使用People p1 = new People(new Man("张三"));和使用Man man = new Man("张三");People p2 = new People(man); 两种不同的方法创建People对象并进行Man赋值,然后进行GC回收;但很明显的结果是:p1回收掉了,p2任然存活。即便是弱引用,里面有对象任然被其他指针指向,则不会被回收。
ThreadLocal中也是一样的,ThreadLocal中的数据不使用后一定要进行remove操作,最好使用匿名创建,第二种情况就会造成GC无法回收,从而造成内存泄漏。

import java.lang.ref.WeakReference;

/**
 * 测试弱引用回收
 */
public class TestThreadLocal03 {

    public static void main(String[] args) {

        //创建People对象
        People p1 = new People(new Man("张三"));//key为弱引用 会被GC回收

        Man man = new Man("张三");
        People p2 = new People(man);//虽然man为弱引用,但是man在外面被new出来了,是强引用

        //进行GC回收
        System.gc();

        try {
            //等待四秒
            System.out.println("等待4秒");
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }


        System.out.println(p1.get());//null 已经被GC回收
        System.out.println(p2.get());//Man[name:张三] 没有被GC回收

    }


    static class People extends WeakReference<Man> {

        public People(Man referent) {
            super(referent);
        }
    }

    static class Man {

        public Man(String name) {
            this.name = name;
        }

        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return new StringBuilder().append("Man[name:" + name + "]").toString();
        }
    }
}

总结:
结构:在每个线程中定义了ThreadLocalMap,是一个map结构,在ThreadLocalMap中是Entry数组,Entry定义keyvalue值,keythreadLocal引用,value为用户传进来的值。
存值:当用户通过threadLocal设置值时,会从当前线程中拿出threadlLocalMap,在threadLocalMap找出对应threadLocalkey,没有,则创建entry并存threadLocalMap,有则覆盖原值。
取值:当用户通过threadlocal进行获取值时,会从当前线程中拿出threadLocalMap,从threadlocalmap中找到对应threadlocal存储的值,然后返回。
ThreadLocal内存泄漏分析
Entry中的key是弱引用,目的是弱引用可以被垃圾回收。
正常情况下使用ThreadLocal,不会被垃圾回收掉,造成内存泄漏;解决:使用完后remove方法释放。

最后,感谢各位的阅读,如有不正确的地方,欢迎评论指正!

评论 44
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值