ThreadLocal源码分析:(二)get()方法
在ThreadLocal的get(),set()的时候都会清除线程ThreadLocalMap里所有key为null的value。 
而ThreadLocal的remove()方法会先将Entry中对key的弱引用断开,设置为null,然后再清除对应的key为null的value。 
本文分析get方法
系列文章链接:
http://www.cnblogs.com/noodleprince/p/8657399.html
http://www.cnblogs.com/noodleprince/p/8658333.html
http://www.cnblogs.com/noodleprince/p/8659028.html
ThreadLocal类的get方法
 public T get() {
     Thread t = Thread.currentThread();
     ThreadLocalMap map = getMap(t);     // 获取线程t中的ThreadLocalMap
     if (map != null) {
         ThreadLocalMap.Entry e = map.getEntry(this);    // 获取entry,见代码1
         if (e != null) {
             @SuppressWarnings("unchecked")
             T result = (T)e.value;
             return result;
         }
     }
     return setInitialValue();   // 没有找到对应的值,调用setInitialValue方法并返回初始值,见代码4
 }
关键逻辑就是去当前线程的ThreadLocalMap中获取对应此ThreadLocal对象的entry,如果获取到了就返回entry的value。否则返回调用setInitialValue方法的结果。
代码1 
ThreadLocal.ThreadLocalMap类的getEntry方法
 private Entry getEntry(ThreadLocal<?> key) {
     int i = key.threadLocalHashCode & (table.length - 1);
     Entry e = table[i];
     if (e != null && e.get() == key)    // 在key计算hash的位置上直接命中查询,直接返回该entry
         return e;
     else
         return getEntryAfterMiss(key, i, e);        // 没有直接命中,调用getEntryAfterMiss,见代码2
 }
如果在key计算hash的位置上直接命中查询,直接返回该entry,否则调用getEntryAfterMiss并返回结果。
代码2 
ThreadLocal.ThreadLocalMap类的getEntryAfterMiss方法
 private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
     Entry[] tab = table;
     int len = tab.length;
     while (e != null) {     // 从i位置开始遍历,寻找key能对应上的entry
         ThreadLocal<?> k = e.get();
         if (k == key)
             return e;
         if (k == null)
             expungeStaleEntry(i);       // 遇到key为null的entry,调用expungeStaleEntry方法,见代码3
         else
             i = nextIndex(i, len);
         e = tab[i];
     }
     return null;        // 实在没有找到,只能返回null了
 }
在从第i个entry向后遍历的过程中,找到对应的key的entry就直接返回,如果遇到key为null的entry,则调用expungeStaleEntry方法进行清理。
代码3 
ThreadLocal.ThreadLocalMap类的expungeStaleEntry方法
 private int expungeStaleEntry(int staleSlot) {
     Entry[] tab = table;
     int len = tab.length;
     // expunge entry at staleSlot
     tab[staleSlot].value = null;
     tab[staleSlot] = null;
     size--;     // 以上代码,将entry的value赋值为null,这样方便GC时将真正value占用的内存给释放出来;将entry赋值为null,size减1,这样这个slot就又可以重新存放新的entry了
     // Rehash until we encounter null
     Entry e;
     int i;
     for (i = nextIndex(staleSlot, len); // 从staleSlot后一个index开始向后遍历,直到遇到为null的entry
          (e = tab[i]) != null;
          i = nextIndex(i, len)) {
         ThreadLocal<?> k = e.get();
         if (k == null) {    // 如果entry的key为null,则清除掉该entry
             e.value = null;
             tab[i] = null;
             size--;
         } else {
             int h = k.threadLocalHashCode & (len - 1);
             if (h != i) {   // key的hash值不等于目前的index,说明该entry是因为有哈希冲突导致向后移动到当前index位置的
                 tab[i] = null;
                 // Unlike Knuth 6.4 Algorithm R, we must scan until
                 // null because multiple entries could have been stale.
                 while (tab[h] != null)      // 对该entry,重新进行hash并解决冲突
                     h = nextIndex(h, len);
                 tab[h] = e;
             }
         }
     }
     return i;   // 返回经过整理后的,位于staleSlot位置后的第一个为null的entry的index值
 }
expungeStaleEntry方法不止清理了staleSlot位置上的entry,还把staleSlot之后的key为null的entry都清理了,并且顺带将一些有哈希冲突的entry给填充回可用的index中。
代码4 
ThreadLocal类的setInitialValue方法
 private T setInitialValue() {
     T value = initialValue();   // initialValue()方法直接返回null
     Thread t = Thread.currentThread();
     ThreadLocalMap map = getMap(t);
     if (map != null)
         map.set(this, value);   // 调用ThreadLocalMap的set方法
     else
         createMap(t, value);    // 创建新的ThreadLocalMap,并将value添加进去
     return value;
 }
setInitialValue方法里面,真正有难度的就是在map不为null时要去调用set方法了。这种情况会在key(也就是ThreadLocal对象)对应的entry已经被清理过后出现,也有可能是一个没有设置过值的ThreadLocal对象来调用get方法,就会进入到这层逻辑。关于ThreadLocalMap的set方法,在另一篇笔记http://www.cnblogs.com/noodleprince/p/8657399.html中有分析过了,这里就不再贴了。
ThreadLocal的get方法,也可能会触发ThreadLocalMap的清理方法,将ThreadLocalMap中key为null的entry给清理掉,方便GC来回收内存。
ThreadLocal源码分析:(二)get()方法的更多相关文章
- 并发-ThreadLocal源码分析
		
ThreadLocal源码分析 参考: http://www.cnblogs.com/dolphin0520/p/3920407.html https://www.cnblogs.com/coshah ...
 - ThreadLocal源码分析-黄金分割数的使用
		
前提 最近接触到的一个项目要兼容新老系统,最终采用了ThreadLocal(实际上用的是InheritableThreadLocal)用于在子线程获取父线程中共享的变量.问题是解决了,但是后来发现对T ...
 - Fresco 源码分析(二) Fresco客户端与服务端交互(1) 解决遗留的Q1问题
		
4.2 Fresco客户端与服务端的交互(一) 解决Q1问题 从这篇博客开始,我们开始讨论客户端与服务端是如何交互的,这个交互的入口,我们从Q1问题入手(博客按照这样的问题入手,是因为当时我也是从这里 ...
 - Java多线程学习之ThreadLocal源码分析
		
0.概述 ThreadLocal,即线程本地变量,是一个以ThreadLocal对象为键.任意对象为值的存储结构.它可以将变量绑定到特定的线程上,使每个线程都拥有改变量的一个拷贝,各线程相同变量间互不 ...
 - Java并发编程之ThreadLocal源码分析
		
## 1 一句话概括ThreadLocal<font face="微软雅黑" size=4> 什么是ThreadLocal?顾名思义:线程本地变量,它为每个使用该对象 ...
 - 并发编程(四)—— ThreadLocal源码分析及内存泄露预防
		
今天我们一起探讨下ThreadLocal的实现原理和源码分析.首先,本文先谈一下对ThreadLocal的理解,然后根据ThreadLocal类的源码分析了其实现原理和使用需要注意的地方,最后给出了两 ...
 - 【JAVA】ThreadLocal源码分析
		
ThreadLocal内部是用一张哈希表来存储: static class ThreadLocalMap { static class Entry extends WeakReference<T ...
 - 框架-springmvc源码分析(二)
		
框架-springmvc源码分析(二) 参考: http://www.cnblogs.com/leftthen/p/5207787.html http://www.cnblogs.com/leftth ...
 - 十、Spring之BeanFactory源码分析(二)
		
Spring之BeanFactory源码分析(二) 前言 在前面我们简单的分析了BeanFactory的结构,ListableBeanFactory,HierarchicalBeanFactory,A ...
 - Vue源码分析(二) : Vue实例挂载
		
Vue源码分析(二) : Vue实例挂载 author: @TiffanysBear 实例挂载主要是 $mount 方法的实现,在 src/platforms/web/entry-runtime-wi ...
 
随机推荐
- 小程序 - swiper除了左右切换还有上下滚动超出屏幕的内容
			
本来呢,我是有专门整理小程序恶心bug的文章的,每次只要添加汇总就好, 但是呢,鉴于这个问题的恶心程度,所以我把他单独拿出来说了. ---------------------------------- ...
 - Tomcat 设置开机自启
			
操作系统centos6.5: Vim /etc/rc.local在末尾添加一下两行 source /etc/profile /test/tomcat/bin/startup.sh 我解释下为什么要加s ...
 - anaconda的fish shell支持
			
最近在用fish shell,但是无法使用conda的activate命令来激活环境.官方给的有解决方案 https://github.com/conda/conda/blob/5b97a96d78e ...
 - 完全卸载hadoop安装的组件(hdp版本)
			
yum remove -y hadoop_* zookeeper* ranger* hbase_* ranger* hbase_* ambari-* hadoop_* zookeeper_* hbas ...
 - devstack部署openstack环境
			
背景:公司需要搭建openstack私有云.配置两台物理服务器. 各大搜索引擎了解了下OpenStack.决定先在虚拟机上部署实现openstack. 前提准备 设备:一台宿主机Windows10 1 ...
 - SQL 存储过程 多条件 分页查询  性能优化
			
最优化查询代码 -- 注意:此处可能会出现 字符串过长问题,所以 必要的情况下请分段处理 set @sql1 =' SELECT * FROM ( select ROW_NUMBER() OVER(O ...
 - 1-7 hibernate关联关系映射
			
1.关联关系分为单向关联(一对一,一对多,多对一,多对多),多向关联(一对一,一对多,多对多). 2.单向一对一主键关联实例 需要为one-to-one元素指定constrained属性值为true. ...
 - JS常用函数用途小记
			
concat() 方法用于连接两个或多个数组. 该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本. var a = [1,2,3]; document.write(a.concat(4,5) ...
 - Redis这些知识点,是必须知道的!
			
Redis是一个开源(BSD许可)的内存数据结构存储,可作为数据库,缓存和消息队列.相比Memcached它支持更多的数据结构,如string(字符串),hash(哈希),list(链表),set(集 ...
 - c++ --> extern "C" {}详解
			
extern "C" {}详解 extern "C"的真实目的是实现类C和C++的混合编程.在C++源文件中的语句前面加上extern "C" ...