在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中有分析过了,这里就不再贴了。

ThreadLocalget方法,也可能会触发ThreadLocalMap的清理方法,将ThreadLocalMap中key为null的entry给清理掉,方便GC来回收内存。

ThreadLocal源码分析:(二)get()方法的更多相关文章

  1. 并发-ThreadLocal源码分析

    ThreadLocal源码分析 参考: http://www.cnblogs.com/dolphin0520/p/3920407.html https://www.cnblogs.com/coshah ...

  2. ThreadLocal源码分析-黄金分割数的使用

    前提 最近接触到的一个项目要兼容新老系统,最终采用了ThreadLocal(实际上用的是InheritableThreadLocal)用于在子线程获取父线程中共享的变量.问题是解决了,但是后来发现对T ...

  3. Fresco 源码分析(二) Fresco客户端与服务端交互(1) 解决遗留的Q1问题

    4.2 Fresco客户端与服务端的交互(一) 解决Q1问题 从这篇博客开始,我们开始讨论客户端与服务端是如何交互的,这个交互的入口,我们从Q1问题入手(博客按照这样的问题入手,是因为当时我也是从这里 ...

  4. Java多线程学习之ThreadLocal源码分析

    0.概述 ThreadLocal,即线程本地变量,是一个以ThreadLocal对象为键.任意对象为值的存储结构.它可以将变量绑定到特定的线程上,使每个线程都拥有改变量的一个拷贝,各线程相同变量间互不 ...

  5. Java并发编程之ThreadLocal源码分析

    ## 1 一句话概括ThreadLocal<font face="微软雅黑" size=4>  什么是ThreadLocal?顾名思义:线程本地变量,它为每个使用该对象 ...

  6. 并发编程(四)—— ThreadLocal源码分析及内存泄露预防

    今天我们一起探讨下ThreadLocal的实现原理和源码分析.首先,本文先谈一下对ThreadLocal的理解,然后根据ThreadLocal类的源码分析了其实现原理和使用需要注意的地方,最后给出了两 ...

  7. 【JAVA】ThreadLocal源码分析

    ThreadLocal内部是用一张哈希表来存储: static class ThreadLocalMap { static class Entry extends WeakReference<T ...

  8. 框架-springmvc源码分析(二)

    框架-springmvc源码分析(二) 参考: http://www.cnblogs.com/leftthen/p/5207787.html http://www.cnblogs.com/leftth ...

  9. 十、Spring之BeanFactory源码分析(二)

    Spring之BeanFactory源码分析(二) 前言 在前面我们简单的分析了BeanFactory的结构,ListableBeanFactory,HierarchicalBeanFactory,A ...

  10. Vue源码分析(二) : Vue实例挂载

    Vue源码分析(二) : Vue实例挂载 author: @TiffanysBear 实例挂载主要是 $mount 方法的实现,在 src/platforms/web/entry-runtime-wi ...

随机推荐

  1. 小程序 - swiper除了左右切换还有上下滚动超出屏幕的内容

    本来呢,我是有专门整理小程序恶心bug的文章的,每次只要添加汇总就好, 但是呢,鉴于这个问题的恶心程度,所以我把他单独拿出来说了. ---------------------------------- ...

  2. Tomcat 设置开机自启

    操作系统centos6.5: Vim /etc/rc.local在末尾添加一下两行 source /etc/profile /test/tomcat/bin/startup.sh 我解释下为什么要加s ...

  3. anaconda的fish shell支持

    最近在用fish shell,但是无法使用conda的activate命令来激活环境.官方给的有解决方案 https://github.com/conda/conda/blob/5b97a96d78e ...

  4. 完全卸载hadoop安装的组件(hdp版本)

    yum remove -y hadoop_* zookeeper* ranger* hbase_* ranger* hbase_* ambari-* hadoop_* zookeeper_* hbas ...

  5. devstack部署openstack环境

    背景:公司需要搭建openstack私有云.配置两台物理服务器. 各大搜索引擎了解了下OpenStack.决定先在虚拟机上部署实现openstack. 前提准备 设备:一台宿主机Windows10 1 ...

  6. SQL 存储过程 多条件 分页查询 性能优化

    最优化查询代码 -- 注意:此处可能会出现 字符串过长问题,所以 必要的情况下请分段处理 set @sql1 =' SELECT * FROM ( select ROW_NUMBER() OVER(O ...

  7. 1-7 hibernate关联关系映射

    1.关联关系分为单向关联(一对一,一对多,多对一,多对多),多向关联(一对一,一对多,多对多). 2.单向一对一主键关联实例 需要为one-to-one元素指定constrained属性值为true. ...

  8. JS常用函数用途小记

    concat() 方法用于连接两个或多个数组. 该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本. var a = [1,2,3]; document.write(a.concat(4,5) ...

  9. Redis这些知识点,是必须知道的!

    Redis是一个开源(BSD许可)的内存数据结构存储,可作为数据库,缓存和消息队列.相比Memcached它支持更多的数据结构,如string(字符串),hash(哈希),list(链表),set(集 ...

  10. c++ --> extern "C" {}详解

    extern "C" {}详解 extern "C"的真实目的是实现类C和C++的混合编程.在C++源文件中的语句前面加上extern "C" ...