1.大概去哪里看

ThreadLocal 其根本实现方法,是在Thread里面,有一个ThreadLocal.ThreadLocalMap属性

ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocalMap 静态内部类维护了一个Entry 数组

private Entry[] table;

查看Entry 源码,它维护了两个属性,ThreadLocal 对象 与一个Object

static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value; Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}

那么,这几项似乎可以这么串下来: Thread. currentThread().threadLocals. table{当前线程,的ThreadLocalMap对象,的Entry数组}(忽略访问权限的事儿)

------------------------------------------------我是分割线------------------------------------------------

2.代码实现分析

ThreadLocal 提供set(),get()方法,用于数据的写入与读取。数据的存储与获取的位置,即
Thread. currentThread().threadLocals. table {当前线程,的ThreadLocalMap对象,的Entry数组}

public void set(T value) {
Thread t = Thread.currentThread();//获取当前线程t
ThreadLocalMap map = getMap(t);//获取threadLocals 对象
if (map != null)
map.set(this, value);//调用 ThreadLocalMap 的set方法向 threadLocals 中写入一条数据
else
createMap(t, value);//如果threadLocals 为null 则为当前线程t 创建一个map,并插入数据
}

map.set(this, value);注意,这里,传入的第一个参数为this 即 ThreadLocal 对象自身,

假如我们声明了一串代码:

private static ThreadLocal<String> threadLocal = new ThreadLocal<String>();

然后我们又执行了 threadLocal.set(“string 1234”);
那么,在Thread. currentThread().threadLocals. table 中,应该有这么一个Entry :ThreadLocal指向threadLocal,value 为 “string 1234”

分析源码(这里,所有的源码都来自于jdk1.7.0_71):

private void set(ThreadLocal<?> key, Object value) {

            // We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not. Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get(); if (k == key) {
e.value = value;
return;
} if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
} tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}

分析两处:
1. int i = key.threadLocalHashCode & (len-1);
根据当前的ThreadLocal 的threadLocalHashCode 跟 ThreadLocalMap.table的长度-1 ,按位与,获得目标索引值 i , 如果tab[i] 为空的话,将会在 tab[i] 处插入一个Entry ;

2. for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)])

如果如果tab[i] 不为空,则调用i = nextIndex(i, len) 将i值进行+1或者置为0,然后判断e是否为null 如果e!=null 判断e 中的 ThreadLocal对象,跟传入的ThreadLocal 对象,是否为同一个对象。如果是同一个对象,则对e的value 进行重新赋值。如果在遍历的过程中发现某个e的ThreadLocal 对象为空,则将Entry(threadLocal,” string 1234”) 设置在此时的tab[i]处。
(如果一开始进来的时候e 为null 即 tab[i]==null 。是不会走for循环的,会直接把Entry(threadLocal,” string 1234”) 赋值到table[i]);

private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}

分析,为什么会有i = nextIndex(i, len) 这样的设定。(2017-01-04 ps:这是一种hash防冲撞的方案)
执行int i = key.threadLocalHashCode & (len-1);的时候,很可能不同的key.threadLocalHashCode得到了相同的 i 值,那么,就从 i 开始,遍历table对象,找到一个可以放置Entry(threadLocal,” string 1234”) 的位置,

比如:

    System.out.println(626627285 & 16-1);//
System.out.println(626627317 & 16-1);//
System.out.println(626627573 & 16-1);//

这三个,获取到的i值,都为5(当然实际用到的hashCode的算法不是这样的,不会产生这么接近的数)。

在同一个线程中,626627285先set(value1)了,得到5,table[5]为空,那就填进去 table[5]=new Entry(626627285,value1);
626627317接着set(value2),算出来i=5,
但是table[5]已经有人占了,那就只能看table[6]有没有空闲位置,一看table[6]==null,好,就放这儿了table[6]=new Entry(626627317,value2);
626627573接着set(value3),算出来i=5,
但是table[5]已经有人占了,那就只能看table[6]有没有空闲位置,一看table[6]也被占了,再看table[7]==null,好,就放这儿了table[7]=new Entry(626627573,value3)
假如有个线程算出来i=15 但是table[15]!=null,需要向后找空闲位置,table[16]是越界的,nextIndex返回0,从table[0]开始找

下面这串代码,维护了table 的长度,避免了遍历了一圈table 却找不到 table[i]==null 的情况,即保证table的某些索引处肯定为null,因为还没填满的时候就已经扩容了。

if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();

------------------------------------------------我是分割线------------------------------------------------

下面分析get()

public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}

1. 获取当前线程的threadLocals 并传入 ThreadLocal 对象,获取对应的值。
2. 如果当前线程的threadLocals 为null ,则为当前线程t 创建一个map,并插入数据setInitialValue ()=null,并返回null

private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}

分析map.getEntry(this)

private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}

这里看到,也是先使用 int i = key.threadLocalHashCode & (table.length - 1); 得到一个索引值,然后去table获取 Entry对象,得到几种结果:
1. e!=null && e.get()!=key 因为是通过“int i = key.threadLocalHashCode & (table.length - 1);”获取的索引值,不同的ThreadLocal 对象,可能获取到相同的索引值,所以,这种情况是存在的。
2. e==null 当前的ThreadLocal 对象 压根儿没有set值。
3. e!=null&&e.get()==key 如果Entry对象的key值与当前传入的ThreadLocal 对象,是同一个对象,则返回e 然后在 get()方法中,返回e.value;
以上 1、2 两种情况的时候,会执行getEntryAfterMiss(key, i, e);

在同一个线程中,626627285、626627317、626627573算到的i值均为5,但是只有626627285==table[5].get(),
626627317、626627573这两个,都需要走getEntryAfterMiss(key, i, e)方法了
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length; while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e;
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}

1. 如果初始出入的e==null 则不进入while 循环直接返回null

2. 在while循环里面,如果e.get()==key 即,e 的Entry对象的key值与当前传入的ThreadLocal 对象,是同一个对象,则返回e 。
3. 如果e.get()==null 的情况下,先将table[i]置为空,然后向后遍历直到
table[nextIndex(i, len)]==null,将table[nextIndex(i, len)]!=null的对象,重新写入table中。
4. k!=key&&key!=null的时候,则调用i = nextIndex(i, len) 将i值进行+1或者置为0,然后判断e是否为null 如果e!=null 判断e 中的 ThreadLocal对象,跟传入的ThreadLocal 对象,是否为同一个对象。如果是同一个对象,返回当前的 Entry对象,如果遇到了e==null的时候,还没有找到目标的Entry ,就返回null 。
为什么找到e==null的地方就可以跳出了呢?
如果如果tab[i] 不为空,则调用i = nextIndex(i, len) 将i值进行+1或者置为0,然后判断e是否为null 如果e!=null 判断e 中的 ThreadLocal对象,跟传入的ThreadLocal 对象,是否为同一个对象。如果是同一个对象,则对e的value 进行重新赋值。如果在遍历的过程中发现某个e的ThreadLocal 对象为空,则将Entry(threadLocal,” string 1234”) 设置在此时的tab[i]处。
这里,插入的Entry(threadLocal,” string 1234”) ,要么,在初始的i 处,要么,往后+1顺延,不会跳过某个索引值,然后进行赋值。所以,当table[i]==null的时候,已经可以不继续找了。

------------------------------------------------我是分割线------------------------------------------------

3 还有一点我觉得很重要的东西

分析到这里的时候,我们应该有发现一个很重要的问题,即:
int i = key.threadLocalHashCode & (table.length - 1);
获得的i值是固定的吗?
很明显不是的,因为table 会扩容,table.length 会变,得到的i值也是不一样的:

System.out.println(626627285 & 16-1);//
System.out.println(626627285 & 32-1);//

这样的话,table[i]岂不是会出错?

然后,仔细阅读源码,在进行扩容的时候,会调用resize()方法:

private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
int newLen = oldLen * 2;
Entry[] newTab = new Entry[newLen];
int count = 0; for (int j = 0; j < oldLen; ++j) {
Entry e = oldTab[j];
if (e != null) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null; // Help the GC
} else {
int h = k.threadLocalHashCode & (newLen - 1);
while (newTab[h] != null)
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
} setThreshold(newLen);
size = count;
table = newTab;
}

在resize() 方法中,我们看到,这里新建了一个长度为原本的长度2倍的Entry数组,然后,将原Entry数组的所有元素,

挨个儿的重新计算索引 int h = k.threadLocalHashCode & (newLen - 1);

然后赋值原有的Entry 到新的Entry 数组中,这样,就保证了数组扩容之后,获取到的 i 值,是适配新数组的正确的值。

继续拿626627285、626627317、626627573举例,这三个,在数组扩容为长度=32的时候,算出来的i值,均为21,那么他们将会是这么算的:
oldTab[5]!=null oldTab[5].get=626627285 算得i=21,table[21]==null table[21]=oldTab[5];
oldTab[6]!=null oldTab[6].get=626627317 算得i=21,table[21]!=null table[22]==null table[22]=oldTab[6];
oldTab[7]!=null oldTab[7].get=626627573 算得i=21,table[21]!=null tanle[22]!=null table[23]==null table[23]=oldTab[7];

------------------------------------------------我是分割线------------------------------------------------

以上,是我个人查看jdk源码,分析出来的ThreadLocal得实现原理与工作原理。欢迎大家批评讨论斧正。谢谢

ThreadLocal 工作原理、部分源码分析的更多相关文章

  1. springmvc工作原理以及源码分析(基于spring3.1.0)

    springmvc是一个基于spring的web框架.本篇文章对它的工作原理以及源码进行深入分析. 一.springmvc请求处理流程 二.springmvc的工作机制 三.springmvc核心源码 ...

  2. 11.深入k8s:kubelet工作原理及源码分析

    转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.com 源码版本是1.19 kubelet信息量是很大的,通过我这一篇文章肯定是讲不全的,大家可 ...

  3. Java并发包中Semaphore的工作原理、源码分析及使用示例

    1. 信号量Semaphore的介绍 我们以一个停车场运作为例来说明信号量的作用.假设停车场只有三个车位,一开始三个车位都是空的.这时如果同时来了三辆车,看门人允许其中它们进入进入,然后放下车拦.以后 ...

  4. 详解SpringMVC中Controller的方法中参数的工作原理[附带源码分析]

    目录 前言 现象 源码分析 HandlerMethodArgumentResolver与HandlerMethodReturnValueHandler接口介绍 HandlerMethodArgumen ...

  5. 【MVC - 参数原理】详解SpringMVC中Controller的方法中参数的工作原理[附带源码分析]

    前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门blog:http://www.cnblogs.com/fangjian0423/p/spring ...

  6. gmock使用、原理及源码分析

    1      初识gmock 1.1      什么是Mock 便捷的模拟对象的方法. 1.2      Google Mock概述 google mock是用来配合google test对C++项目 ...

  7. OpenCV学习笔记(27)KAZE 算法原理与源码分析(一)非线性扩散滤波

    http://blog.csdn.net/chenyusiyuan/article/details/8710462 OpenCV学习笔记(27)KAZE 算法原理与源码分析(一)非线性扩散滤波 201 ...

  8. ConcurrentHashMap实现原理及源码分析

    ConcurrentHashMap实现原理 ConcurrentHashMap源码分析 总结 ConcurrentHashMap是Java并发包中提供的一个线程安全且高效的HashMap实现(若对Ha ...

  9. HashMap和ConcurrentHashMap实现原理及源码分析

    HashMap实现原理及源码分析 哈希表(hash table)也叫散列表,是一种非常重要的数据结构,应用场景及其丰富,许多缓存技术(比如memcached)的核心其实就是在内存中维护一张大的哈希表, ...

  10. (转)ReentrantLock实现原理及源码分析

    背景:ReetrantLock底层是基于AQS实现的(CAS+CHL),有公平和非公平两种区别. 这种底层机制,很有必要通过跟踪源码来进行分析. 参考 ReentrantLock实现原理及源码分析 源 ...

随机推荐

  1. java Web项目创建之一(普通java web项目的创建与发布)

    1.创建新的web项目 file->new_>Dynamic Web Project(如图) 或file->new->Project->Web->Dynamic W ...

  2. 前端常用的WindowsCMD命令

    前面的话   在网上找了一些关于命令提示符CMD的资料,但是很多资料都是把所有的功能罗列出来,大部分都不会用到.所以,自己把常用的CMD命令总结如下,方便查阅 操作类 help 列出所有支持的指令及说 ...

  3. H3 BPM让天下没有难用的流程之功能介绍

    H3 BPM10.0功能地图如下:  图:H3 BPM 功能地图 一.流程引擎 H3  BPM 流程引擎遵循WFMC 标准的工作流引擎技术,设计可运行的流程和表单,实现工作任务在人与人.人与系统.系统 ...

  4. iOS开发--ChildViewController实现订单页的切换

    先不说废话, 上效果图, 代码量也不大, 也不上传github骗星星了, 你们复制粘贴下代码, 就可以轻而易举的弄出一个小demo. 这个代码的实现并不复杂, 甚至于说非常简单, 就是逻辑有点小绕, ...

  5. SQL SERVER导入数据到ORACLE的方法总结

    我们偶尔会有将数据从SQL SERVER导入到ORACLE当中的这种需求,那么这种跨数据库导数有那些方法呢?这些方法又有那些利弊呢? 下面比较肤浅的总结了一些可行的方法. 1:生成SQL脚本然后去OR ...

  6. 跟着老男孩教育学Python开发【第一篇】:初识Python

    Python简介 Python前世今生 Python的创始人为吉多·范罗苏姆(Guido van Rossum).1989年的圣诞节期间,吉多·范罗苏姆为了在阿姆斯特丹打发时间,决心开发一个新的脚本解 ...

  7. 两个变量交换的四种方法(Java)

    对于两种变量的交换,我发现四种方法,下面我用Java来演示一下. 1.利用第三个变量交换数值,简单的方法. (代码演示一下) class TestEV //创建一个类 { public static ...

  8. Linux学习

    Linux 命令英文全称su:Swith user 切换用户,切换到root用户cat: Concatenate 串联uname: Unix name 系统名称df: Disk free 空余硬盘du ...

  9. Ubuntu 16.04 安装 arm-linux-gcc 嵌入式交叉编译环境 问题汇总

    闲扯: 实习了将近半年一直在做硬件以及底层的驱动,最近要找工作了发现了对linux普遍要求很高,而且工作岗位也非常多,所以最近一些时间在时不时地接触linux. 正文:(我一时兴起开始写博客,准备不充 ...

  10. Git时间(第一次写,这个怎么玩啊)

    1.安装 Liunx直接打开shell界面,输入:sudo apt-get install git-core ,按下回车之后输入密码即可完成安装: Windows系统在https://git-for- ...