ThreadLocal源码分析

来源:互联网 发布:apache bench post 编辑:程序博客网 时间:2024/05/19 03:29

原文地址:http://www.cnblogs.com/xrq730/p/4854813.html

ThreadLocal源码剖析
ThreadLocal其实比较简单,因为类里就三个public方法:set(T value)、get()、remove()。先剖析源码清楚地知道ThreadLocal是干什么用的、再使用、最后总结,讲解ThreadLocal采取这样的思路。
三个理论基础
在剖析ThreadLocal源码前,先讲一下ThreadLocal的三个理论基础:
1、每个线程都有一个自己的ThreadLocal.ThreadLocalMap对象
2、每一个ThreadLocal对象都有一个循环计数器
3、ThreadLocal.get()取值,就是根据当前的线程,获取线程中自己的ThreadLocal.ThreadLocalMap,然后在这个Map中根据第二点中循环计数器取得一个特定value值
上边三点会在下边的代码中具体分析:
两个数学问题
1、ThreadLocal.ThreadLocalMap规定了table的大小必须是2的N次幂。

2、Hash增量设置为0x61c88647,也就是说ThreadLocal通过取模的方式取得table的某个位置的时候,会在原来的threadLocalHashCode的基础上加上0x61c88647
set(T value)
一点点看set方法的源码:
和前面讲的一样:
1、取得当前的线程
2、获取线程里面的ThreadLocal.ThreadLocalMap
3、看这个ThreadLocal.ThreadLocalMap是否存在,存在就设置一个值,不存在就给线程创建一个ThreadLocal.ThreadLocalMap
第三点有两个分支,先看简单的创建Map的分支:
这个Map中并没有next节点,所以,不得不说ThreadLocalMap是一个有点误导性的名字,它虽然叫做Map,但其实存储的方式不是链表法而是开地址法。看到设置table中的位置的时候,都把一个static的nextHashCode累加一下,这意味着,set的同一个value,可能在每个ThreadLocal.ThreadLocalMap中的table中的位置都不一样,不过这没关系。
OK,看完了创建的分支,看一下设置的分支:
理一下逻辑,设置的时候做了几步:
1、先对ThreadLocal里面的threadLocalHashCode取模获取到一个table中的位置
2、这个位置上如果有数据,获取这个位置上的ThreadLocal
(1)判断一下位置上的ThreadLocal和我本身这个ThreadLocal是不是一个ThreadLocal,是的话数据就覆盖,返回
(2)不是同一个ThreadLocal,再判断一下位置上的ThreadLocal是不是空的,这个解释一下。Entry是ThreadLocal弱引用,"static class Entry extends WeakReference<ThreadLocal>",有可能这个ThreadLocal被垃圾回收了,这时候把新设置的value替换到当前位置上,返回
(3)上面都没有返回,给模加1,看看模加1后的table位置上是不是空的,是空的再加1,判断位置上是不是空的...一直到找到一个table上的位置不是空的为止,往这里面塞一个value。换句话说,当table的位置上有数据的时候,ThreadLocal采取的是办法是找最近的一个空的位置设置数据
get()
如果理解清楚了set(T value),get()方法就很好理解了:
理一下步骤:
1、获取当前线程
2、尝试去当前线程中拿它的ThreadLocal.ThreadLocalMap
3、当前线程中判断是否有ThreadLocal.ThreadLocalMap
(1)有就尝试根据当前ThreadLocal的threadLocalHashCode取模去table中取值,有就返回,没有就给模加1继续找,这和设置的算法是一样的
(2)没有就调用set方法给当前线程ThreadLocal.ThreadLocalMap设置一个初始值
remove()
remove()方法就非常简单了:
取得当前线程的ThreadLocal.ThreadLocalMap,如果有ThreadLocal.ThreadLocalMap,找到对应的Entry,移除掉就好了。

总结
上面分析了这么多源码,是比较细节地来看ThreadLocal了。对这些内容做一个总结,ThreadLocal的原理简单说应该是这样的:
  1. ThreadLocal不需要key,因为线程里面自己的ThreadLocal.ThreadLocalMap不是通过链表法实现的,而是通过开地址法实现的
  2. 每次set的时候往线程里面的ThreadLocal.ThreadLocalMap中的table数组某一个位置塞一个值,这个位置由ThreadLocal中的threadLocaltHashCode取模得到,如果位置上有数据了,就往后找一个没有数据的位置
  3. 每次get的时候也一样,根据ThreadLocal中的threadLocalHashCode取模,取得线程中的ThreadLocal.ThreadLocalMap中的table的一个位置,看一下有没有数据,没有就往下一个位置找
  4. 既然ThreadLocal没有key,那么一个ThreadLocal只能塞一种特定数据。如果想要往线程里面的ThreadLocal.ThreadLocalMap里的table不同位置塞数据 ,比方说想塞三种String、一个Integer、两个Double、一个Date,请定义多个ThreadLocal,ThreadLocal支持泛型"public class ThreadLocal<T>"。 
  5. 应用场景:当很多线程需要多次使用同一个对象,并且需要该对象具有相同初始化值的时候最适合使用ThreadLocal。
  6. 其实说再多也不如看一下源码来得清晰。如果要看源码,其中涉及到一个WeakReference和一个Map,这两个地方需要了解下,这两个东西分别是a.Java的弱引用,也就是GC的时候会销毁该引用所包裹(引用)的对象,这个threadLocal作为key可能被销毁,但是只要我们定义成他的类不卸载,tl这个强引用就始终引用着这个ThreadLocal的,永远不会被gc掉。b.和HashMap差不多。
  7. 事实上,从本质来讲,就是每个线程都维护了一个map,而这个map的key就是threadLocal,而值就是我们set的那个值,每次线程在get的时候,都从自己的变量中取值,既然从自己的变量中取值,那肯定就不存在线程安全问题,总体来讲,ThreadLocal这个变量的状态根本没有发生变化,他仅仅是充当一个key的角色,另外提供给每一个线程一个初始值。如果允许的话,我们自己就能实现一个这样的功能,只不过恰好JDK就已经帮我们做了这个事情。
8. ThreadLocal不是集合,它不存储任何内容,真正存储数据的集合在Thread中。ThreadLocal只是一个工具,一个往各个线程的ThreadLocal.ThreadLocalMap中table的某一位置set一个值的工具而已
9、同步与ThreadLocal是解决多线程中数据访问问题的两种思路,前者是数据共享的思路后者是数据隔离的思路.
ThreadLocal除了适用于多线程保证每条线程都有自己的变量副本外,还适用于在线程上下文中共享某些变量值。
这两种说法是有区别的。前者强调的是,使用ThreadLocal对副本做保护,避免同步、加锁,降低效率;后者强调的是,某个变量线程上下文中,A处用到、B处用到、C处用到,先在入口处set一个值,后使用ThreadLocal的get方法直接在需要用到的地方拿这个值。
ThreadLocal的作用只是一个工具,一个往线程的ThreadLocal.ThreadLocalMap塞指定数据的一个工具而已。

原创粉丝点击