深入Java并发编程(四):ThreadLocal与ThreadLocalMap。

前言

通常情况下,我们创建的变量是可以被任何线程访问和修改的,但有时线程会希望有专属于自己的本地变量来进行一些操作。ThreadLocal类正是为了满足这一需求而被提出的。ThreadLocal类主要解决的就是对于变量,每个线程都可以有这个变量的副本,在对变量副本读写时都不会对其它的副本造成影响。

一、ThreadLocal示例

我们准备通过这个实例来论证ThreadLocal修饰的变量确实可以在不同的线程中有自己的变量副本。

 我们设计一个UserSession类用来存储用户登录信息的bean。

再提供SessionCache将每个用户的session分别存储到它们的线程中 

 

这里我们使用了ThreadLocal类,泛型用的UserSession。下面我们去测试一下,当不同的线程将它们中的用户信息存储到SessionCache类中的session中时,ThreadLocal能不能起到使每个线程都有session变量副本的效果,下面请看测试用例:

我们分别开启了t1线程和t2线程,t1和t2存储着不同的用户session,我们把它们都存储到SessionCache的静态ThreadLocal变量session中,然后再从session中取出并输出用户信息。

我们可以发现,session对于t1线程和t2线程输出的值是不同的,这也证明了ThreadLocal变量在每个线程中都有自己的变量副本。那接下来我们就来说一下它的原理吧。

二、ThreadLocal原理

我们在使用时肯定会思考这样一个问题,为什么用ThreadLocal修饰的变量,可以在不同的线程中创建自己的变量副本呢?我们来看下面一段Thread类中的源码:

从上面的源码中我们可以看到,Thread类中有ThreadLocalMap类型的变量,分别是threadLocals和inheritableThreadLocals变量

。而ThreadLocalMap是实际存放变量副本的容器,我们来看ThreadLocal类的源码,了解ThreadLocal是怎么把值存放到线程中的:

  
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);//(1)
        if (map != null)
            map.set(this, value);//(2)
        else
            createMap(t, value);//(3)
    }

    //这里的t就是当前线程对象
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

 当我们调用ThreadLocal的set方法时,他会先从当前线程的ThreadLocalMap中获取到map对象,然后判断这个map是否为null,如果没有就使用createMap()方法在当前线程中创建ThreadLocalMap对象并给map添加一个键值对,其中键就是当前的ThreadLocal类型,而值就是想要存储的value。如果已经被创建过(已经存储过其他的ThreadLocal变量),那就直接往map里添加键值对就行了。而ThreadLocal变量和ThreadLocalMap中的键值对也可以认为是多对多的关系,ThreadLocal变量可以往不同线程中的ThreadLocalMap中添加自己的变量副本,而ThreadLocalMap也可以接收不同的ThreadLocal变量。

        //ThreadLocalMap的键值对类型
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            ......
        }

 

三、内存泄漏

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

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

我们可以看到,ThreadLocalMap中存放ThreadLocal对象的key是弱引用的,也就是说,如果这个key在没有外部强引用来引用它的时候,在下一次的gc时势必会被回收。而在作为key的ThreadLocal被回收后,value却因为是强引用而继续存在,因此ThreadLocalMap就会出现多个key为的Entry。这样的话在线程未结束前当前线程就会有一条指向value的强引用链导致导致这些Entry无法被回收,从而造成内存泄露。

不过ThreadLocalMap的设计者高瞻远瞩,已经想好了防护措施:在ThreadLocalMap执行get()、set()和remove()的时候都会去清除该线程中ThreadLocalMap中所有key为null的value。

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值