文章目录

  • 1. ThreadLocal的内部结构:
    • 1.1 常见误解:
    • 1.2 核心结构:
    • 1.3 这样设计的好处:
  • 2. ThreadLocal的核心方法源码:
    • 2.1 get方法:
    • 2.2 set方法:
    • 2.3 remove方法:
    • 2.4 initialValue方法:
  • 3.ThreadLocalMap码源分析:
    • 3.1基本结构:
    • 3.2对于hash冲突问题:

1. ThreadLocal的内部结构:

1.1 常见误解:

通常,如果我们不去看源代码的话,我猜ThreadLocal是这样子设计的:每个ThreadLocal类都创建一个Map,然后用线程的ID threadID作为Mapkey,要存储的局部变量作为Mapvalue,这样就能达到各个线程的局部变量隔离的效果。这是最简单的设计方法,JDK最早期的ThreadLocal就是这样设计的。

1.2 核心结构:

但是,JDK后面优化了设计方案,现时JDK8 ThreadLocal的设计是:每个Thread维护一个ThreadLocalMap哈希表,这个哈希表的keyThreadLocal实例本身,value才是真正要存储的值Object

(1) 每个Thread线程内部都有一个Map (ThreadLocalMap)
(2) Map里面存储ThreadLocal对象(key)和线程的变量副本(value)
(3)Thread内部的Map是由ThreadLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值。
(4)对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干扰。

1.3 这样设计的好处:

(1)这样设计之后每个Map存储的Entry数量就会变少,因为之前的存储数量由Thread的数量决定,现在是由ThreadLocal的数量决定。

(2) 当Thread销毁之后,对应的ThreadLocalMap也会随之销毁,能减少内存的使用。

2. ThreadLocal的核心方法源码:

除了构造之外, ThreadLocal对外暴露的方法有以下4个:

方法声明 描述
protected T initialValue() 返回当前线程局部变量的初始值
public void set( T value) 设置当前线程绑定的局部变量
public T get() 获取当前线程绑定的局部变量
public void remove() 移除当前线程绑定的局部变量
2.1 get方法:

(1)码源:

    /*** 返回当前线程中保存ThreadLocal的值* 如果当前线程没有此ThreadLocal变量,* 则它会通过调用{@link #initialValue} 方法进行初始化值** @return 返回当前线程对应此ThreadLocal的值*/public T get() {// 获取当前线程对象Thread t = Thread.currentThread();// 获取此线程对象中维护的ThreadLocalMap对象ThreadLocalMap map = getMap(t);// 如果此map存在if (map != null) {// 以当前的ThreadLocal 为 key,调用getEntry获取对应的存储实体eThreadLocalMap.Entry e = map.getEntry(this);// 找到对应的存储实体 e if (e != null) {@SuppressWarnings("unchecked")// 获取存储实体 e 对应的 value值// 即为我们想要的当前线程对应此ThreadLocal的值T result = (T)e.value;return result;}}// 如果map不存在,则证明此线程没有维护的ThreadLocalMap对象// 调用setInitialValue进行初始化return setInitialValue();}/*** set的变样实现,用于初始化值initialValue,* 用于代替防止用户重写set()方法* @return the initial value 初始化后的值*/private T setInitialValue() {// 调用initialValue获取初始化的值T value = initialValue();// 获取当前线程对象Thread t = Thread.currentThread();// 获取此线程对象中维护的ThreadLocalMap对象ThreadLocalMap map = getMap(t);// 如果此map存在if (map != null)// 存在则调用map.set设置此实体entrymap.set(this, value);else// 1)当前线程Thread 不存在ThreadLocalMap对象// 2)则调用createMap进行ThreadLocalMap对象的初始化// 3)并将此实体entry作为第一个值存放至ThreadLocalMap中createMap(t, value);// 返回设置的值valuereturn value;}/*** 获取当前线程Thread对应维护的ThreadLocalMap * @param  t the current thread 当前线程* @return the map 对应维护的ThreadLocalMap */ThreadLocalMap getMap(Thread t) {return t.threadLocals;}/***创建当前线程Thread对应维护的ThreadLocalMap * @param t 当前线程* @param firstValue 存放到map中第一个entry的值*/void createMap(Thread t, T firstValue) {//这里的this是调用此方法的threadLocalt.threadLocals = new ThreadLocalMap(this, firstValue);}

(2 ) 代码执行流程

A. 首先获取当前线程

B. 根据当前线程获取一个Map

C. 如果获取的Map不为空,则在Map中以ThreadLocal的引用作为key来在Map中获取对应的value e,否则转到E

D. 如果e不为null,则返回e.value,否则转到E

E. Map为空或者e为空,则通过initialValue函数获取初始值value,然后用ThreadLocal的引用和value作为firstKey和firstValue创建一个新的Map

总结: 先获取当前线程的 ThreadLocalMap 变量,如果存在则返回值,不存在则创建并返回初始值。

2.2 set方法:

(1)码源和对应的中文注释:

/*** 设置当前线程对应的ThreadLocal的值** @param value 将要保存在当前线程对应的ThreadLocal的值*/public void set(T value) {// 获取当前线程对象Thread t = Thread.currentThread();// 获取此线程对象中维护的ThreadLocalMap对象ThreadLocalMap map = getMap(t);// 如果此map存在if (map != null)// 存在则调用map.set设置此实体entrymap.set(this, value);else// 1)当前线程Thread 不存在ThreadLocalMap对象// 2)则调用createMap进行ThreadLocalMap对象的初始化// 3)并将此实体entry作为第一个值存放至ThreadLocalMap中createMap(t, value);}

(2)执行流程:

A. 首先获取当前线程,并根据当前线程获取一个Map

B. 如果获取的Map不为空,则将参数设置到Map中(当前ThreadLocal的引用作为key)

C. 如果Map为空,则给该线程创建 Map,并设置初始值

2.3 remove方法:

(1)码源及其中文注释:

/*** 删除当前线程中保存的ThreadLocal对应的实体entry*/public void remove() {// 获取当前线程对象中维护的ThreadLocalMap对象ThreadLocalMap m = getMap(Thread.currentThread());// 如果此map存在if (m != null)// 存在则调用map.remove// 以当前ThreadLocal为key删除对应的实体entrym.remove(this);}

(2)执行流程:

A. 首先获取当前线程,并根据当前线程获取一个Map

B. 如果获取的Map不为空,则移除当前ThreadLocal对象对应的entry

2.4 initialValue方法:

(1)码源:

/*** 返回当前线程对应的ThreadLocal的初始值* 此方法的第一次调用发生在,当线程通过{@link #get}方法访问此线程的ThreadLocal值时* 除非线程先调用了 {@link #set}方法,在这种情况下,* {@code initialValue} 才不会被这个线程调用。* 通常情况下,每个线程最多调用一次这个方法。** <p>这个方法仅仅简单的返回null {@code null};* 如果程序员想ThreadLocal线程局部变量有一个除null以外的初始值,* 必须通过子类继承{@code ThreadLocal} 的方式去重写此方法* 通常, 可以通过匿名内部类的方式实现** @return 当前ThreadLocal的初始值*/
protected T initialValue() {return null;
}

(2)分析:

此方法的作用是 返回该线程局部变量的初始值。

(1) 这个方法是一个延迟调用方法,从上面的代码我们得知,在set方法还未调用而先调用了get方法时才执行,并且仅执行1次。

(2)这个方法缺省实现直接返回一个null

(3)如果想要一个除null之外的初始值,可以重写此方法。(备注: 该方法是一个protected的方法,显然是为了让子类覆盖而设计的)

3.ThreadLocalMap码源分析:

3.1基本结构:

ThreadLocalMap是ThreadLocal的内部类,没有实现Map接口,用独立的方式实现了Map的功能,其内部的Entry也是独立实现。

(1) 成员变量

/*** 初始容量 —— 必须是2的整次幂*/private static final int INITIAL_CAPACITY = 16;/*** 存放数据的table,Entry类的定义在下面分析* 同样,数组长度必须是2的冥。*/private Entry[] table;/*** 数组里面entrys的个数,可以用于判断table当前使用量是否超过负因子。*/private int size = 0;/*** 进行扩容的阈值,表使用量大于它的时候进行扩容。*/private int threshold; // Default to 0/*** 阈值设置为长度的2/3*/private void setThreshold(int len) {threshold = len * 2 / 3;}

(2) 存储结构 - Entry

// 在ThreadLocalMap中,也是用Entry来保存K-V结构数据的。但是Entry中key只能是ThreadLocal对象,这点被Entry的构造方法已经限定死了
// 另外,Entry继承WeakReference,使用弱引用,可以将ThreadLocal对象的生命周期和线程生命周期解绑,持有对ThreadLocal的弱引用,可以使得ThreadLocal在没有其他强引用的时候被回收掉,这样可以避免因为线程得不到销毁导致ThreadLocal对象无法被回收static class Entry extends WeakReference<ThreadLocal> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal k, Object v) {super(k);value = v;}
}
3.2对于hash冲突问题:

(1)ThreadLocal的set() 方法

public void set(T value) {Thread t = Thread.currentThread();ThreadLocal.ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);}ThreadLocal.ThreadLocalMap getMap(Thread t) {return t.threadLocals;}void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocal.ThreadLocalMap(this, firstValue);}
  1. 代码很简单,获取当前线程,并获取当前线程的ThreadLocalMap实例(从getMap(Thread t)中很容易看出来)。
  2. 如果获取到的map实例不为空,调用map.set()方法,否则调用构造函数 ThreadLocal.ThreadLocalMap(this, firstValue)实例化map。
  3. 可以看出来线程中的ThreadLocalMap使用的是延迟初始化,在第一次调用get()或者set()方法的时候才会进行初始化。

(2)构造函数ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue)

 ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {//初始化tabletable = new ThreadLocal.ThreadLocalMap.Entry[INITIAL_CAPACITY];//计算索引int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);//设置值table[i] = new ThreadLocal.ThreadLocalMap.Entry(firstKey, firstValue);size = 1;//设置阈值setThreshold(INITIAL_CAPACITY);}

主要说一下计算索引,firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1)

  1. 关于& (INITIAL_CAPACITY - 1),这是取模的一种方式,对于2的幂作为模数取模,用此代替%(2^n),这也就是为啥容量必须为2的冥,在这个地方也得到了解答。

  2. 关于firstKey.threadLocalHashCode

  3. private final int threadLocalHashCode = nextHashCode();

     private final int threadLocalHashCode = nextHashCode();private static int nextHashCode() {return nextHashCode.getAndAdd(HASH_INCREMENT);}private static AtomicInteger nextHashCode =  new AtomicInteger();private static final int HASH_INCREMENT = 0x61c88647;
    

这里定义了一个AtomicInteger类型,每次获取当前值并加上HASH_INCREMENT,HASH_INCREMENT = 0x61c88647,这个值和斐波那契散列有关(这是一种乘数散列法,只不过这个乘数比较特殊,是32位整型上限2^32-1乘以黄金分割比例0.618…的值2654435769,用有符号整型表示就是-1640531527,去掉符号后16进制表示为0x61c88647),其主要目的就是为了让哈希码能均匀的分布在2的n次方的数组里, 也就是Entry[] table中,这样做可以尽量避免hash冲突。

(3)ThreadLocalMap中的set()

ThreadLocalMap使用开发地址-线性探测法来解决哈希冲突,线性探测法的地址增量di = 1, 2, … 其中,i为探测次数。该方法一次探测下一个地址,直到有空的地址后插入,若整个空间都找不到空余的地址,则产生溢出。假设当前table长度为16,也就是说如果计算出来key的hash值为14,如果table[14]上已经有值,并且其key与当前key不一致,那么就发生了hash冲突,这个时候将14加1得到15,取table[15]进行判断,这个时候如果还是冲突会回到0,取table[0],以此类推,直到可以插入。

按照上面的描述,可以把table看成一个环形数组

先看一下线性探测相关的代码,从中也可以看出来table实际是一个环:

/*** 获取环形数组的下一个索引*/private static int nextIndex(int i, int len) {return ((i + 1 < len) ? i + 1 : 0);}/*** 获取环形数组的上一个索引*/private static int prevIndex(int i, int len) {return ((i - 1 >= 0) ? i - 1 : len - 1);}

ThreadLocalMap的set()的代码:

private void set(ThreadLocal<?> key, Object value) {ThreadLocal.ThreadLocalMap.Entry[] tab = table;int len = tab.length;//计算索引,上面已经有说过。int i = key.threadLocalHashCode & (len-1);/*** 根据获取到的索引进行循环,如果当前索引上的table[i]不为空,在没有return的情况下,* 就使用nextIndex()获取下一个(上面提到到线性探测法)。*/for (ThreadLocal.ThreadLocalMap.Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {ThreadLocal<?> k = e.get();//table[i]上key不为空,并且和当前key相同,更新valueif (k == key) {e.value = value;return;}/*** table[i]上的key为空,说明被回收了* 这个时候说明改table[i]可以重新使用,用新的key-value将其替换,并删除其他无效的entry*/if (k == null) {replaceStaleEntry(key, value, i);return;}}

并且和当前key相同,更新value
if (k == key) {
e.value = value;
return;
}
/**
* table[i]上的key为空,说明被回收了
* 这个时候说明改table[i]可以重新使用,用新的key-value将其替换,并删除其他无效的entry
*/
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}


ThreadLocal深析相关推荐

  1. 第四范式陈雨强:万字深析工业界机器学习最新黑科技 By 机器之心2017年7月25日 16:38 近日,全球最顶级大数据会议 Strata Data Conference 在京召开。Strata 大

    第四范式陈雨强:万字深析工业界机器学习最新黑科技 By 机器之心2017年7月25日 16:38 近日,全球最顶级大数据会议 Strata Data Conference 在京召开.Strata 大会 ...

  2. Android——Framework之Packamanager深析

    Android--Framework之Packamanager深析 今天我们来介绍下  最下面的流程总结 ,同为Framework层的Packagemanager类(基于Android2.3.3源码分 ...

  3. Apache中间件漏洞深析

    Apache中间件漏洞深析 Apache介绍 运行原理介绍 apche解析php流程 Apache攻击手段 换行解析漏洞 多后缀解析漏洞 SSI远程命令执行漏洞 Apache介绍 Apache是世界使 ...

  4. maven和gradle深析

    maven和gradle深析 本质 gradle使用的是真正的脚本语言groovy(很关键一点就是它也是编译为class运行在jvm上的),maven使用的是标签语言xml(本质是就是符合预定的文本) ...

  5. Java SE 027 String类源代码深析

    Java SE 027 String类源代码深析 前言:此笔记为圣思园张龙老师讲述的java视频课程笔记,自己看视频学习时记录的,用于积累与复习,在此分享给学习软件编程的兄弟姐妹们,以供参考. 1.e ...

  6. 【数组】深析 “数组名称”

    0w0  例子引入  一.不求甚解  二.求甚解   1.数组名和指针的区别   2.数组名在哪里   3.求甚解   4.下标表达式 和 指针表达式 说是"深析",我也不知道够不 ...

  7. python3进阶篇(二)——深析函数装饰器

    python3进阶篇(二)--深析函数装饰器 前言: 阅读这篇文章我能学到什么?   装饰器可以算python3中一个较难理解的概念了,这篇文章由浅入深带你理解函数装饰器,请阅读它. --如果您觉得这 ...

  8. ThreadLocal 源码深析及使用示例

    在开始看源码之前,我们必须要知道 ThreadLocal 有什么作用:ThreadLocal 使同一个变量在不同线程间隔离,即每个线程都可以有自己独立的副本,然后可以在该线程的方法间共享(随时取出使用 ...

  9. 第四范式陈雨强:万字深析工业界机器学习最新黑科技

    雷锋网(公众号:雷锋网)按:近日,全球最顶级大数据会议Strata Data Conference在京召开.Strata大会被<福布斯>杂志誉为"大数据运动的里程碑", ...

最新文章

  1. 拉杰尔安卓服务器注册上限,拉结尔多开养小号刷副本 用多多云手机离线能升级...
  2. 汇编解析(4)-BIOS
  3. 创建订单 - 保存订单与子订单数据
  4. FPGA嵌入式处理器的选择策略
  5. High ASCII字符从bat文件到dos控制台的转化问题
  6. Zabbix实战-简易教程--拓扑图(Maps)
  7. 汉化:Blocs for Mac(可视化网页设计工具)4.5.0
  8. 常用图像处理相关图像数据库
  9. 微博如何发订阅消息_微博怎么添加订阅 - 卡饭网
  10. java 淘口令_简单实现淘口令
  11. foxmail超大附件服务器文件怎么删,电脑中使用Foxmail发送超大附件的方法
  12. 小武学fpgastep3
  13. 反编译之脱去乐固加固的壳
  14. list添加元素_如何给List集合的每个元素添加index序号
  15. Linux报错-ssh_exchange_identi...
  16. Qualcomm msm8996 调试AMOLED屏
  17. 斐讯的服务器不稳定,K1不稳定的原因找到了!原因是CPU时钟频率太高,要降频!...
  18. Android三种模拟器介绍
  19. Linux的电源管理-休眠与唤醒
  20. 【强化学习论文合集】三十一.2021智能体和多智能体系统国际联合会议论文(AAMAS2021)

热门文章

  1. 图网络embeding transE及node2vec方法
  2. 百度同步盘linux客户端,技术|Linux下百度云的Python客户端(支持Unicode)
  3. 【错误记录】Ubuntu 下 VSCode 编译报错 ( 无法生成和调试,因为活动文件不是 C 或 C++ 源文件。终端进程启动失败(退出代码: -1)。终端将被任务重用,按任意键关闭。 )
  4. 区块链投资入门篇—骨灰版
  5. 学会做笔记-子弹笔记学习概要四
  6. 机构推荐24只中线成长股
  7. 转义字符 lt; gt; 等等
  8. java英文介绍范文_java的英文自我介绍范文
  9. 将系统必备组件打包进安装文件(以vs15打包.net formwork 4.5为例)有点鸡肋
  10. 利用python进行png图像的读写操作