ThreadLocal源码学习笔记
一丶ThreadLocal结构
每一个Thread对象都有一个名为threadLocals
类型为ThreadLocal.ThreadLocalMap
的属性,ThreadLocal.ThreadLocalMap
对象内部存在一个Entry
数组其中存储的Entry对象key是ThreadLocal
,value便是我们绑定在线程上的值。ThreadLocal之所以可以做到线程隔离是由于每一个线程对象持有一个ThreadLocalMap,每一个线程对ThreadLocalMap的处理是互不影响的。之所以持有的是ThreadLocalMap,是线程可能使用多个ThreadLocal存储数据,比如在Spring事务同步管理器中TransactionSynchronizationManager
包含三个ThreadLocal对象,一个管理事务相关资源,一个管理当前事务需要回调的同步接口,一个管理事务名称,三个ThreadLocal对象对应着当前Thread
持有的ThreadLocal.ThreadLocalMap
中Entry数组的的三个Entry
二丶源码学习
1.set(T value)——向ThreadLocal中设置值
拿到当前线程Thread.currentThread()
这是一个Native方法,getMap
方法便是获取线程中的ThreadLocal.ThreadLocalMap threadLocals
属性,包装成方法便于子类重写覆盖。如果当前线程的ThreadLocalMap
不为空那么向ThreadLocalMap
中设置值,反之调用createMap
初始化map。通常第一次设置值的时候ThreadLocalMap
为空。
2.createMap(Thread t, T firstValue)——为线程初始化ThreadLocalMap
方法很简单直接调用ThreadLocalMap的构造函数,在研究此构造函数之前我们先看下ThreadLocalMap的结构,其包含一个Entry
数组,其中Entry继承了WeakReference
2.1为什么这里Entry保存ThreadLocal类型的key使用弱引用:
我们知道弱引用具备的性质:在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。这里使用弱应用是为了防止oom,如果ThreadLocal作为Key不使用弱引用,且根据可达性算法此ThreadLocal以及无法和GCRoot关联,但是当前线程并没有结束,可以通过当前线程关联到其ThreadLocalMap
,再关联到Entry中的ThreadLocal对象,这时候ThreadLocal将永远无法被回收。
这里我们给出一个启动线程执行死循环,再死循环中创建ThreadLocal并set,这段代码执行并不会发生OOM,原因是ThreadLocal是被弱引用指向,再发生GC的时候会被回收。
这里应该还有一个问题,虽然ThreadLocal被回收了,但是Entry数组一致再塞入Entry,回收之后就相当于Entry的key为null,value存在值,那么为什么不会oom昵,原因是往ThreadLocalMap中塞入元素的时候,会删除掉过时(指Entry中的key弱引用持有的ThreadLocal为null)的元素。
2.2 ThreadLocalMap构造方法
这里使用到ThreadLocal.threadLocalHashCode
此值由nextHashCode
方法生成,其使用AtomicInteger
原子类生成
其中firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1
是为了让hash分布均匀减少hash冲突(类似于HashMap中高位地位进行异或),至于为什么使用0x61c88647
我没有深究。
setThreshold
方法是使用属性threshold
记录当前Entry数组长度的2/3
作为扩容阈值,扩容逻辑后续进行解析。
3.ThreadLocalMap#set(ThreadLocal<?> key, Object value) 存入数据
set方法的逻辑可以分作两部分:1.使用开放地址法找到合适的位置存储数据,2.向数组中放入新Entry,有需要的话扩容
3.1.使用开放地址法找到合适的位置存储数据
第一个if 意味着是相同的ThreadLocal,类似于HashMap put相同key的元素多次,后续的后覆盖前面的,这里也一样,进行覆盖。
第二个if 意味着,原来霸占Entry数组位置的ThreadLocal弱应用持有的ThreadLocal被回收了会调用replaceStaleEntry
覆盖
3.2向数组中放入新Entry,有需要的话扩容
上面for循环进行的条件是e != null
,e是Entry数组中元素,那么结束for循环,除了成功覆盖原有元素的还有找到一个可以使用的位置
这里扩容的条件有两个cleanSomeSlots
删除过期的条目失败,且 当前Entry数组存入元素大于扩容阈值
扩容代码如下,遍历所有的元素,如果已经被回收了那么将value也置为null,如果没有被回收那么将元素拷贝到新的位置
这里为什么要将value也置为空昵
首先ThreadLocal的key 已经被回收了,这时候调用者没办法拿到被回收key对应的value,所有置为null是不会影响到使用的。
关键的是Help the GC
的注释,置为null可以帮助jvm进行GC,我们首先看下如下方法
此方法也不会发生OOM
理论直接将被回收Entry位置的元素置为null,这时候也是无法通过GC Root应用到Entry,自然也无法引用到String对象,直接置为null也是相应的目的
这里扩容复制元素没有像HashMap进行地位不变,高位翻倍的操作,还是使用开放地址法找到合适的位置。
4.ThreadLocal#get()——获取和当前线程绑定在此ThreadLocal上的值
这部分代码分为两部分看:
4.1获取ThreadLocalMap中的值
获取当前线程中的ThreadLocalMap属性,以当前ThreadLocal作为key获取到对应的值,具体获取的逻辑在ThreadLocalMap#getEntry
方法
首先是对Entry数组的长度进行取模,获取当前ThreadLocal对应的位置,如果存在,且Entry中的ThreadLocal和当前入参的ThreadLocal相同(之所以需要这么判断是因为,hash冲突后当前ThreadLocal会被放在后续的位置,只有二者的地址相同才能返回),那么返回。之所以判断e!=null
可能是当前线程先删除再get,这时候不判断会抛出空指针。
getEntryAfterMiss
方法并不复杂,就是利用nextIndex
找下一个位置,类似于HashMap中拉链法需要遍历链表一样,如果下一个位置为null,说明当前ThreadLocal没有存储过,直接返回null
4.2ThreadLocalMap没有初始化,或者没有从ThreadLocalMap中获取到对应的值
这里会直接调用setInitialValue
方法
其中initialValue()
方法是给子类复写提供的方法,我们可以如下为ThreadLocal设置初始值
也可以使用ThreadLocal提供的静态工厂方法,如
使用此静态方法返回的是SuppliedThreadLocal
其initialValue
方法会调用传入的Supplie,两种方法都可以自定义ThreadLocal没有设置值的时候返回的初始值
5.ThreadLocal#remove()
首先自然是获取当前线程的ThreadLocalMap,如果初始化了才进行删除,然后调用ThreadLocalMap#remove
方法,把当前ThreadLocal作为key
删除过期条目的expungeStaleEntry
方法,会将Entry数组中过期的条目(弱引用被回收,或者被删除的条目)置为null。
三丶InheritableThreadLocal支持继承的ThreadLocal
这里说的继承是指父线程往InheritableThreadLocal
设置了值,然后父线程开启子线程,子线程的InheritableThreadLocal
会拷贝其中的值
如上图,运行test5()
方法的线程是main线程,首先向其中设置值parent
,然后开启子线程,子线程运行直接使用get并打印出parent
。具体原理是Thread
的构造方法会拿到当前线程中的inheritableThreadLocals
内容复制到子线程的inheritableThreadLocals
中
这里调用了ThreadLocal.createInheritedMap(parent.inheritableThreadLocals)
将返回值设置到创建线程的inheritableThreadLocals
属性上
逻辑也很简单,遍历父线程中的entry元素,调用childValue
方法,实现父Entry值映射成子Entry值(InheritableThreadLocal
默认直接信息映射,如有需要可以覆盖childValue
方法),然后使用开放地址法存到子线程中。
其中InheritableThreadLocal
还重写了getMap
,createMap
方法,二者都操作Thread中的inheritableThreadLocals
属性
ThreadLocal源码学习笔记的更多相关文章
- Spring 源码学习笔记10——Spring AOP
Spring 源码学习笔记10--Spring AOP 参考书籍<Spring技术内幕>Spring AOP的实现章节 书有点老,但是里面一些概念还是总结比较到位 源码基于Spring-a ...
- Spring 源码学习笔记11——Spring事务
Spring 源码学习笔记11--Spring事务 Spring事务是基于Spring Aop的扩展 AOP的知识参见<Spring 源码学习笔记10--Spring AOP> 图片参考了 ...
- Spring源码学习笔记12——总结篇,IOC,Bean的生命周期,三大扩展点
Spring源码学习笔记12--总结篇,IOC,Bean的生命周期,三大扩展点 参考了Spring 官网文档 https://docs.spring.io/spring-framework/docs/ ...
- Underscore.js 源码学习笔记(下)
上接 Underscore.js 源码学习笔记(上) === 756 行开始 函数部分. var executeBound = function(sourceFunc, boundFunc, cont ...
- Underscore.js 源码学习笔记(上)
版本 Underscore.js 1.9.1 一共 1693 行.注释我就删了,太长了… 整体是一个 (function() {...}()); 这样的东西,我们应该知道这是一个 IIFE(立即执行 ...
- AXI_LITE源码学习笔记
AXI_LITE源码学习笔记 1. axi_awready信号的产生 准备接收写地址信号 // Implement axi_awready generation // axi_awready is a ...
- Hadoop源码学习笔记(6)——从ls命令一路解剖
Hadoop源码学习笔记(6) ——从ls命令一路解剖 Hadoop几个模块的程序我们大致有了点了解,现在我们得细看一下这个程序是如何处理命令的. 我们就从原头开始,然后一步步追查. 我们先选中ls命 ...
- Hadoop源码学习笔记(5) ——回顾DataNode和NameNode的类结构
Hadoop源码学习笔记(5) ——回顾DataNode和NameNode的类结构 之前我们简要的看过了DataNode的main函数以及整个类的大至,现在结合前面我们研究的线程和RPC,则可以进一步 ...
- Hadoop源码学习笔记(4) ——Socket到RPC调用
Hadoop源码学习笔记(4) ——Socket到RPC调用 Hadoop是一个分布式程序,分布在多台机器上运行,事必会涉及到网络编程.那这里如何让网络编程变得简单.透明的呢? 网络编程中,首先我们要 ...
随机推荐
- openssl客户端编程:一个不起眼的函数导致的SSL会话失败问题
我们目前大部分使用的openssl库还是基于TLS1.2协议的1.0.2版本系列,如果要支持更高的TLS1.3协议,就必须使用openssl的1.1.1版本或3.0版本.升级openssl库有可能会导 ...
- 关于Java中的构造方法
关于构造方法: 1.构造方法又叫构造函数/构造器. 2.构造方法语法结构中"返回值类型"不需要指定,也不能写void,如若写void,则变成普通方法. 3.构造方法有返回值,和当前 ...
- FDFS上传文件报错 tracker_query_storage fail, error no: 2, error info: No such file or directo
原因: 1.tracker服务没有启动 2.Storage服务没有启动 解决方案: 输入命令查看这两个服务是否启动,如果没有则表明没有启动.启动即可. netstat -tulnp tracker服务 ...
- NC16462 [NOIP2015]跳石头
NC16462 [NOIP2015]跳石头 题目 题目描述 一年一度的"跳石头"比赛又要开始了! 这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石.组委会已经选择好了两块 ...
- # Vue3 toRef 和 toRefs 函数
Vue3 toRef 和 toRefs 函数 上一篇博文介绍了 vue3 里面的 ref 函数和 reactive 函数,实现响应式数据,今天主要来说一下 toRef 函数和 toRefs 函数的基本 ...
- win10设置Python程序定时运行(设置计划任务)
今天来设置一下定时执行Pycharm内的脚本: 这个要基于win10 的任务计划程序(设置 > 控制面板 > 系统和安全 > 管理工具 > 任务计划程序) 1. create ...
- P2599 [ZJOI2009]取石子游戏 做题感想
题目链接 前言 发现自己三岁时的题目都不会做. 我发现我真的是菜得真实. 正文 神仙构造,分讨题. 不敢说有构造,但是分讨我只服这道题. 看上去像是一个类似 \(Nim\) 游戏的变种,经过不断猜测结 ...
- Conversation Modeling on Reddit Using a Graph-Structured LSTM
publish: Transactions of the Association for Computational Linguistics,2016 tasks: predicting popul ...
- 线程池的概念&原理和线程池的代码实现
线程池:一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作, 无需反复创建线程而消耗过多资源.工作原理:可以用一张图来简洁明了说明: 合理利用线程池能够带来三个好处∶1.降低 ...
- jdbc 03:注册驱动的方式
jdbc连接mysql时,注册驱动的方式 package com.examples.jdbc.o3_注册驱动方式; //mysql驱动所在的包 import com.mysql.jdbc.Driver ...