深入理解Threadlocal的实现原理

文章开头我想说,这是一篇面向不怎么懂  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( ) 方法:

[java] view plain copy

  1. * @return the initial value for this thread-local  
  2.      */  
  3. protected T initialValue() {  
  4. return null;  
  5.     }  

我们看 jdk 的注释 ,返回的是本地线程变量的初始值。返回值为空的原因很简单,这个方法就是用来重写的嘛~

再来看 get 方法:

[java] view plain copy

  1. public T get() {  
  2.        Thread t = Thread.currentThread();  
  3.        ThreadLocalMap map = getMap(t);  
  4. if (map != null) {  
  5.            ThreadLocalMap.Entry e = map.getEntry(this);  
  6. if (e != null) {  
  7. @SuppressWarnings("unchecked")  
  8.                T result = (T)e.value;  
  9. return result;  
  10.            }  
  11.        }  
  12. return setInitialValue();  
  13.    }  

代码解析:1:获取当前所在线程  

                 2:通过当前线程当做key 得到一个叫  ThreadLocalMap 的家伙 

                 3: 如果存在就从这个ThreadLocalMap 里面去取得它的enter对象

  4:返回 enter对象保存的value。  

从我们的使用经验来看,这个value肯定就是我们要用的那个要被隔离的变量了。

但 ThreadLocalMap 又是个什么呢? 先不要着急,我们先看 ThreadLocalMap 为空的结果,进去 setInitialValue() 方法 看看:

从注释可以看到,该方法是用来代替set()方法 设置初始值的。

我们逐一分析:

[java] view plain copy

  1. /**
  2.   * Variant of set() to establish initialValue. Used instead
  3.   * of set() in case user has overridden the set() method.
  4.   *
  5.   * @return the initial value
  6.   */
  7. private T setInitialValue() {  
  8.      T value = initialValue();  //去initiaValue()方法拿值,而 initiaValue方法是空的,也就是
  9.      Thread t = Thread.currentThread();   //说不重写的话,就得自己去set值。
  10.      ThreadLocalMap map = getMap(t);  //还是去 这个 ThreapLocalMap 里面去看看有没有那个变量
  11. if (map != null)    //如果map不为空就 将value 替换 (但我们是先从get方法尽来的)所以不会走这里
  12.          map.set(this, value);  
  13. else
  14.          createMap(t, value);   该方法会创建一个map,当前线程作为K,重写后initialValue的值是V  
  15. return value;  
  16.  }  

我们再看看createMap 做了什么:

[java] view plain copy

  1. void createMap(Thread t, T firstValue) {  
  2.         t.threadLocals = new ThreadLocalMap(this, firstValue);  
  3.     }  

创建了一个 ThreadLocalMap 对象,并且返回的是 一个线程的变量。

我们再逐一分析这个点:

这个线程(t)哪来的?

createMap方法是ThreadLocal 里调用的,这个线程 t   就是 ThreadLocal 的 所在的线程, 

 指的是 ThreadLocalMap 所在的那个(当前)线程 ,value 指的是 我们要隔离的 那个变量。 再看看   ThreadLocalMap  纠结又是个什么东西 。 继续看 ThreadLocalMap 的源码:

[java] view plain copy

  1. static class ThreadLocalMap {  
  2. static class Entry extends WeakReference<ThreadLocal<?>> {  
  3. /** The value associated with this ThreadLocal. */
  4.             Object value;  
  5.             Entry(ThreadLocal<?> k, Object v) {  
  6. super(k);  
  7.                 value = v;  
  8.             }  
  9.         }  
  10. -----------省略若干不重要的代码---------  
  11. ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {  
  12.             table = new Entry[INITIAL_CAPACITY];  
  13. int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);  
  14.             table[i] = new Entry(firstKey, firstValue);  
  15.             size = 1;  
  16.             setThreshold(INITIAL_CAPACITY);  
  17.         }  

我们可以看到 ThreadLocalMap 内部是一个entry对象, 还是 K : V 形式存在的。它用的是当前所在线程的  ThreadLocal , 作为key  要隔离的那个变量作为value。  并且 这个entry 对象 继承了 弱引用类型 这个类。(这里是引起内存泄漏的点,ThreadLocal的第二部分会再提及这里的细节。)

好的,看到这里,估计肯定有很多人还是蒙圈的,这里我们暂停梳理一下。

画了个图来说一下这个~

ThreadLocalMap  它保存的是当前的 ThreadLocal对象 和 当前线程 操作的那个 要被隔离的变量 value 。

这个 ThreadLocalMap 在被创建的时候,它是保存在当前线程(Thread)中的。  因为

[java] view plain copy

  1. void createMap(Thread t, T firstValue) {  
  2.         t.threadLocals = new ThreadLocalMap(this, firstValue);  
  3.     }  

然后我们我们将目光回到get( )方法最开始的前二行:

Thread t = Thread.currentThread();         ThreadLocalMap map = getMap(t);

这里get 方法 通过 当前所在线程 得到了一个 ThreadLocalMap ,我们看看 这个getMap(t)干了什么。 下面展示两段很关键的代码。

getMap( ) 是在 ThreadLocal这个类当;

[java] view plain copy

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

这个成员变量的定义是在Thread类当中;

[java] view plain copy

  1. /* ThreadLocal values pertaining to this thread. This map is maintained
  2.     * by the ThreadLocal class. */
  3.    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方法可以容易多了:

[java] view plain copy

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

这里已经不需要再过多说明了吧?因为get 方法的流程已经详细说明了,还不懂的朋友 建议跟着我的解释再把get方法在源码中多走几遍。

到此为止:set() , get() ,  initialValue()已经全部分析完了。还差一个remove方法,该方法是jdk1.5之后才有的。这个方法有什么用呢?

带着疑问:我们还是进入主题三,先弄懂 Threadlocal  造成内存泄漏的原因 吧~

<二>深入理解面试常问的Threadlocal 关于内存泄漏的思考

如果你工作一年以上,看完一遍还是不懂的话一定得跟着上面的图还有博客的思路再过一遍源码;很简单的东西,我说得比较啰嗦,

完全是希望帮不懂的人弄懂。。。。。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏数据结构与算法

HDU 1848 Fibonacci again and again(SG函数)

Problem Description 任何一个大学生对菲波那契数列(Fibonacci numbers)应该都不会陌生,它是这样定义的: F(1)=1; ...

3486
来自专栏偏前端工程师的驿站

Design Pattern: Not Just Mixin Pattern

Brief                                 从Mix-In模式到Mixin模式,中文常用翻译为“混入/织入模式”。单纯从名字上看...

1996
来自专栏数据库

httpclient如何快速往数据库里添加测试用例

本人在使用httpclient做接口测试的过程中,使用数据库管理用例,其中存的key-value的形式,由于接口的参数可能比较多,所以一个个用例写起来会比较麻烦...

1939
来自专栏流媒体

C语言内存模型

1583
来自专栏代码世界

Python之递归函数

递归函数 初识递归函数 递归函数的定义:在一个函数里再调用这个函数本身 Python为了考虑保护内存占用情况,有一个递归深度的限制。 探究递归的默认最大深度: ...

2856
来自专栏瓜大三哥

HLS Lesson5-如何处理任意精度的数据类型

传统C语言支持的数据类型: 字符型,整型(有符号和无符号),浮点型等 数据都是以8为边界,但是RTL的位宽就比较随意 ? 数据都是以8为边界,但是RTL的位宽就...

2468
来自专栏NetCore

[原创]Fluent NHibernate之旅(三)-- 继承

经过了“开篇”和“简单映射”两篇文章,相信大家对Fluent NHibernate 有了一定的了解了,FluentNHibernate实际就是对 NHibern...

1988
来自专栏java学习

每日一练(2017/5/14)

Java基础 | 数据库 | Android | 学习视频 | 学习资料下载 课前导读 ●回复“每日一练”获取以前的题目! ●答案公布时间:为每期发布题目的第二...

2425
来自专栏撸码那些事

【封装那些事】 未利用封装

1604
来自专栏walterlv - 吕毅的博客

为什么委托的减法(- 或 -=)可能出现非预期的结果?(Delegate Subtraction Has Unpredictable Result)

2017-12-28 02:03

791

扫码关注云+社区

领取腾讯云代金券