文章开头我想说,这是一篇面向不怎么懂 Threadlocal 的朋友的博客,所以有的人会觉得有点啰嗦,但不论您水平高低,相信耐着性子看完也一定会有收获。
上次去深圳的一家大型的互联网金融公司面试,就被问到了 Threadlocal , 当时只是在代码里看到过用它来管理session。
第一次看到的时候,当时觉得这么高深的东西还是以后去研究吧,结果就是面试官的一脸鄙视。。。
花了一天时间好好看了一下源码,终于算是了解了Threadlocal了。
不得不提,最近看源码没有刚开始的时候吃力了,当然也可能是Threadlocal的源码太简单了吧~
很久以前看hashmap的时候,看到有的作者博客里也实现了一个简单的 Threadlocal ,一看,哇塞,好简单,就 几个方法而已,然而还是不懂它的实现原理。
最后面有个图,是后来加进去的,可以先看图再理解,没放前面来,是怕不懂关系的朋友一开始看不明白。
话不多说,进入主题,本文 使用的是 jdk1.8;
1、Threadlocal有什么用。
2、抱着什么样的目的去看/去熟悉/去理解 Threadlocal 的源码。
3、Threadlocal 造成内存泄漏的原因。
4、思考、总结学习成果。
一、 Threadlocal 有什么用?
Threadlocal 是为了使各个线程都有一份自己独立的 变量/对象 , 而不是 用来解决共享对象的多线程访问问题的。
Threadlocal 的 对象的使用 类似于 hashmap 的使用,但它没有实现map接口,更不是hashmap的数据结构,
它也是可以像hashmap 一样的 保存 K : V 的键值对,但是一个 Threadlocal 只能保存一个 这样的键值对。并且每个线程直接的数据都不会收到干扰。那究竟是为什么呢? 我们现在一探究竟~
首先 Threadlocal 主要有 set() , get() , remove() , initialValue();这几个方法。
二、抱着什么样的目的去看/去熟悉/去理解 Threadlocal 的源码?
如果是看源码的话,看看有哪些方法,怎么调用API就好了。
想要熟悉和理解 Threadlocal 的源码的话,我建议先思考这么三个问题:
1、 Threadlocal 为什么能实现每个线程能有一个独立的变量副本;
2、每个线程的变量副本的储存位置在哪儿;
3、变量副本是如何从共享变量中复制出来的;
首先我们来看 initialValue( ) 方法:
* @return the initial value for this thread-local
*/
protected T initialValue() {
return null;
}
我们看 jdk 的注释 ,返回的是
本地线程变量的初始值。返回值为空的原因很简单,这个方法就是用来
重写的嘛~
再来看 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();
}
代码解析:1:获取当前所在线程
2:通过当前线程当做key 得到一个叫 ThreadLocalMap 的家伙
3: 如果存在就从这个ThreadLocalMap 里面去取得它的enter对象
4:返回 enter对象保存的value。
从我们的使用经验来看,这个value肯定就是我们要用的那个要被隔离的变量了。
但 ThreadLocalMap 又是个什么呢?
先不要着急,我们先看 ThreadLocalMap 为空的结果,进去 setInitialValue() 方法 看看:
从注释可以看到,该方法是用来代替set()方法 设置初始值的。
我们逐一分析:
/**
* Variant of set() to establish initialValue. Used instead
* of set() in case user has overridden the set() method.
*
* @return the initial value
*/
private T setInitialValue() {
T value = initialValue(); //去initiaValue()方法拿值,而 initiaValue方法是空的,也就是
Thread t = Thread.currentThread(); //说不重写的话,就得自己去set值。
ThreadLocalMap map = getMap(t); //还是去 这个 ThreapLocalMap 里面去看看有没有那个变量
if (map != null) //如果map不为空就 将value 替换 (但我们是先从get方法尽来的)所以不会走这里
map.set(this, value);
else
createMap(t, value); 该方法会创建一个map,当前线程作为K,重写后initialValue的值是V
return value;
}
我们再看看createMap 做了什么:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
创建了一个 ThreadLocalMap 对象,并且返回的是 一个线程的变量。
我们再逐一分析这个点:
这个线程(t)哪来的?
createMap方法是ThreadLocal 里调用的,这个线程 t 就是 ThreadLocal 的 所在的线程,
指的是 ThreadLocalMap 所在的那个(当前)线程 ,value 指的是 我们要隔离的 那个变量。
再看看 ThreadLocalMap 纠结又是个什么东西 。 继续看 ThreadLocalMap 的源码:
static class ThreadLocalMap {
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<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
我们可以看到
ThreadLocalMap 内部是一个entry对象, 还是 K : V 形式存在的
。它用的是
当前所在线程的 ThreadLocal , 作为key 要隔离的那个变量作为value。 并且 这个entry 对象 继承了 弱引用类型 这个类。(这里是引起内存泄漏的点,ThreadLocal的第二部分会再提及这里的细节。)
好的,看到这里,估计肯定有很多人还是蒙圈的,这里我们暂停梳理一下。
画了个图来说一下这个~
ThreadLocalMap 它保存的是当前的 ThreadLocal对象 和 当前线程 操作的那个 要被隔离的变量 value 。
这个 ThreadLocalMap 在被创建的时候,它是保存在当前线程(Thread)中的。 因为
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
然后我们我们将目光回到get( )方法最开始的前二行:
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
这里get 方法 通过 当前所在线程 得到了一个 ThreadLocalMap ,我们看看 这个getMap(t)干了什么。
下面展示两段很关键的代码。
getMap( ) 是在 ThreadLocal这个类当;
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
这个成员变量的定义是在Thread类当中;
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
如果之前不懂,但现在又看懂了上面代码流程的朋友,不知道你们看到这里有没有一种恍然大悟的感觉。反正我是有的~
为什么恍然大悟呢?
因为 我们遇到了 Threadlocal 、Thread、 ThreadlocalMap;
Thread 的成员变量 threadLocals 就是 Threadlocal 的get(thread) 方法 找到的 ThreadlocalMap对象啊!!!
所以带着疑问看源码前的三个问题全解决了!!!
1、每个线程的变量副本的储存位置在哪儿?
ThreadlocalMap 就是用来保存每个线程的变量副本的,构造方法的 第一个参数(K)是 当前的所在线程,第二个参数(V)就是变量副本。
2、 Threadlocal 为什么能实现每个线程能有一个独立的变量副本;
因为 ThreadlocalMap存在 Threadlocal这个类当中,你调用get 或者 set 方法的时候,如果没有这个 ThreadlocalMap,他就会自己创建。 并且这个 (假)map的key 是当前线程对象 都是不唯一的。 所以每个线程都能有一个独立的变量副本。
3、变量副本是如何从共享变量中复制出来的;
重写 initialValue( )方法 或者 在调用set ( ) 时创建的 。
跟着get方法走了一遍源码,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 方法的流程已经详细说明了,还不懂的朋友 建议跟着我的解释再把get方法在源码中多走几遍。
到此为止:set() , get() , initialValue()已经全部分析完了。还差一个remove方法,该方法是jdk1.5之后才有的。这个方法有什么用呢?
带着疑问:我们还是进入主题三,先弄懂 Threadlocal 造成内存泄漏的原因 吧~
<二>深入理解面试常问的Threadlocal 关于内存泄漏的思考
如果你工作一年以上,看完一遍还是不懂的话一定得跟着上面的图还有博客的思路再过一遍源码;很简单的东西,我说得比较啰嗦,
完全是希望帮不懂的人弄懂。。。。。