在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. 云计算之路-阿里云上:3个manager节点异常造成 docker swarm 集群宕机

    今天 11:29 - 11:39 左右,docker swarm 集群 3 个 manager 节点同时出现异常,造成整个集群宕机,由此给您带来很大的麻烦,请您谅解. 受此次故障影响的站点有:博问,闪 ...

  2. c#多线程同步之Semaphore

    一提到Semaphore(信号量)的使用,还挺有意思的,它允许多个线程同时访问多个稀有资源,我立马想到银行的ATM机取钱的场景.看下面的代码: ); public static void StartT ...

  3. c# List实现原理

    在研究前辈们写的代码,总是搞不明白.word文中引文的索引和引文列表中的索引对应关系是什么呢?是如何对应上的?我冥思苦想,昨天又系统地看了下代码,才所有悟,所以记录下我的探索过程. 如下图所示: 图1 ...

  4. java容器类2:Map及HashMap深入解读

    Java的编程过程中经常会和Map打交道,现在我们来一起了解一下Map的底层实现,其中的思想结构对我们平时接口设计和编程也有一定借鉴作用.(以下接口分析都是以jdk1.8源码为参考依据) 1. Map ...

  5. windows下编译caffe报错:error MSB4062: 未能从程序集 E:\NugetPackages\OpenCV.2.4.10\......的解决办法

    参考博客:http://blog.csdn.net/u013277656/article/details/75040459 在windows上编译caffe时,用vs打开后会自动加载还原NugetPa ...

  6. 本地安装plsql和instantclient连接linux服务器端的oracle

    当虚拟机里面的linux服务器安装好oracle之后(具体安装方法在之前的博客有说到),如何连接使用呢?连接过程中会出现什么问题呢? 1.首先我们下载instantclient的压缩包,直接解压就好, ...

  7. Spring源码学习:第1步--在Spring源码中添加最简单的Demo代码

    为了最大程度地贴近Spring源码并进行学习,一种比较直接的做法是:直接在Spring源码中加入Demo代码,并进行调试. 参照以前使用Spring的经验,Spring最简单的使用方法是:一个实体类. ...

  8. Cloudera Manager及CDH最新版本安装全程记录

    大家都知道,Apache Hadoop的配置很繁琐,而且很零散,为此Cloudera公司提供了Clouder Manager工具,而且还封装了Apache Hadoop,flume,spark,hiv ...

  9. ASCII代码

    ASCII(American Standard Code for Information Interchange,美国标准信息交换代码)是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧 ...

  10. Java中的懒汉式单例与饿汉式单例实例详解

    懒汉式单例:线程非安全,当被调用的时候才创建实例,效率较高 public class LazySingleton { private static LazySingleton lazySingleto ...