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类详解的主要内容,如果未能解决你的问题,请参考以下文章