ThreadLocal类详解

Posted 敲代码的小小酥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ThreadLocal类详解相关的知识,希望对你有一定的参考价值。

ThreadLocal类注释翻译

打开JDK中ThreadLocal类源码,翻译类上注释如下(提取重点部分):

每个访问ThreadLocal实例对象的线程都有其自己的关于ThreadLocal对象的变量副本(通过get和set方法),只要线程存活而且ThreadLocal对象也存活,则线程都保留着与ThreadLocal对象的变量副本的隐式引用。线程停止后,其对应的threadlocal变量副本会被垃圾回收。

上面的翻译其实就表达了一个意思,即每个线程有自己的ThreadLocal对象,且Thread对象对ThreadLocal对象进行了隐式引用。如何进行的隐式引用呢?继续往下研究。

Entry解析

Entry是ThreadLocalMap类的一个内部类,其类注释如下:

该类对其key值ThreadLocal对象进行了弱引用,空键(即entry.get()==null)意味着该键不再被引用,因此可以从表中删除该项。在接下来的代码中,这些条目被称为“过时条目”。

看Entry实现:

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

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

Entry持有了ThreadLocal的弱引用。即Entry对象没有被GC回收,但是key值ThreadLocal对象可以被GC回收,此时这个Entry对象的key就是null,称为"过时条目"。

ThreadLocalMap解析

ThreadLocalMap是一个定制的哈希映射,仅适用于维护线程本地值。ThreadLocal没有对外获取ThreadLocalMap对象的操作。ThreadLocalMap类是私有的,允许在Thread类中声明字段。为了处理非常大和长期的使用,hash entries 对key值使用弱引用。由于使用了弱引用,所以只有当entries空间满后,才会删除过时的entries。

上面是ThreadLocalMap类注释翻译。从上面翻译可以看出,ThreadLocal中对Thread本地存值,其实都是存到了ThreadLocalMap中。
在Thread类中,声明了ThreadLocalMap的变量,阅读Thread类源码,可知变量为:

/* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

由注释可知,这个成员变量由ThreadLocal类维护。即threadLocals变量的值,在ThreadLocal类中赋予。

下面看ThreadLocalMap中table属性:

 private Entry[] table;

table属性存储了本地线程中key-value键值对数组,因为一个线程可以存放多个key-value,所以是数组形式。上面提到,Entry的key是ThreadLocal对象,所以,在Thread本地变量中,key就是ThreadLocal对象,value就是我们要存的值,而这个key-value对象又存放在ThreadLocalMap中的Entry对象中。在Thread类中,有ThreadLocalMap对象的属性,这样就可以获取到任意存放的本地变量。这样这个功能的逻辑就形成了闭环。

但是jdk在设计这个功能时有些特殊,甚至说有些别扭。因为我们在给一个线程对象存取本地变量时,都是通过ThreadLocal对象的get()和set()方法来进行操作的。而我们上面又提到,ThreadLocal是线程对象本地变量Map中的一个key。用key来操作Map,把自己当成key存到Map中,这个操作是不是很另类呢?

ThreadLocal类方法

initialValue()方法

 protected T initialValue() 
        return null;
    

初始化值。用于没有给ThreadLocal对象调用set()方法赋值,而直接调用get()方法取值时进行返回。即创建了ThreadLocal对象后,没有给其set()值,则调用get()时返回initialValue()方法的值。这个方法一般在创建ThreadLocal时通过内部类方式进行实现。

get()方法

public T get() 
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) 
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) 
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            
        
        return setInitialValue();
    

先看第一行,获取到当前线程,即谁调用ThreadLocal对象的get()方法,就获取到谁的线程,
第二行调用getMap()方法,参数是当前线程,看getMap()方法源码:

ThreadLocalMap getMap(Thread t) 
        return t.threadLocals;
    

返回了当前线程的threadLocals属性,这个属性我们上面分析过,就是当前线程的ThreadLocalMap,而这个Map中存放了当前线程的本地变量。

然后,调用ThreadLocalMap的getEntry()方法,参数是this,即当前的ThreadLocal对象,以此为key,找对应value。

总体流程就是: ThreadLocal作为key,它的get()方法首先获取到当前线程,然后再获取到当前线程的本地变量Map,即ThreadLocalMap,然后在把ThreadLocal自己作为key,传入ThreadLocalMap的getEntry()方法中,获取对应的value。这个设计别扭吧。

set()方法

  public void set(T value) 
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    

跟get()方法逻辑类似,获取到当前线程,然后获取到当前线程的ThreadLocalMap成员属性,然后调用Map的set方法,key就是ThreadLocal对象本身,value是参数设置的值。set()进去的key和value最终加入到Entry[] table中去。

关于ThreadLocal内存泄漏问题

Thread类里引用了ThreadLocalMap对象,ThreadLocalMap中Entry对象对ThreadLocal对象进行了弱引用。所以自始至终,Thread对象和ThreadLocal对象都没有发生直接关系。所以,ThreadLocal对象在出栈后,会被GC。此时,Entry中key为null。但是value还是有值的。所以,这个Entry就在内存中,无法获取了。这就是内存溢出问题。示意图如下:

那为什么Entry要设置成对ThreadLocal的弱引用呢?为何不设置成强引用呢?因为如果设置成弱引用,有可能发生内存溢出,如果设置成强引用,Entry[]不被GC,ThreadLocal就不会GC。假如ThreadLocal没用了,也不会被回收,肯定造成了内存溢出。
采用弱引用后,ThreadLocal可以被回收了,就进行回收,虽然有造成内存溢出可能,但是不是一定会造成内存溢出。而且在ThreadLocal对象的set()和get()方法中,会检查当前Thread的ThreadLocalMap中是否有key为null的Entry清除掉。
想要避免ThreadLocal内存溢出问题,就在程序中及时调用remove()方法,及时删除掉不用的Entry对象,这样就不会有内存溢出问题了。

以上是关于ThreadLocal类详解的主要内容,如果未能解决你的问题,请参考以下文章

ThreadLocal类详解

Handler消息机制关键类详解

Handler消息机制关键类详解

Java并发系列03ThreadLocal详解

ThreadLocal 详解

ThreadLocal详解