ThreadLocal源码解析及实战应用
作者:京东物流 闫鹏勃
1 什么是ThreadLocal?
ThreadLocal是一个关于创建线程局部变量的类。
通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。而使用ThreadLocal创建的变量只能被当前线程访问,其他线程则无法访问和修改。ThreadLocal在设计之初就是为解决并发问题而提供一种方案,每个线程维护一份自己的数据,达到线程隔离的效果。
2 有什么作用?
2.1 set once,get everywhere
在现在的系统设计中,前后端分离已基本成为常态,分离之后如何获取用户信息就成了一件麻烦事,通常在用户登录后, 用户信息会保存在Session或者Token中。这个时候,我们如果使用常规的手段去获取用户信息会很费劲,拿Session来说,我们要在接口参数中加上HttpServletRequest对象,然后调用 getSession方法,且每一个需要用户信息的接口都要加上这个参数,才能获取Session,这样实现就很麻烦了。
在实际的系统设计中,我们肯定不会采用上面所说的这种方式,而是使用ThreadLocal,我们会选择在拦截器的业务中, 获取到保存的用户信息,然后存入ThreadLocal,那么当前线程在任何地方如果需要拿到用户信息都可以使用ThreadLocal的get()方法 (异步程序中ThreadLocal是不可靠的)
2.2 线程安全,空间换时间
在Spring的Web项目中,我们通常会将业务分为Controller层,Service层,Dao层, 我们都知道@Autowired注解默认使用单例模式,那么不同请求线程进来之后,由于Dao层使用单例,那么负责数据库连接的Connection也只有一个, 如果每个请求线程都去连接数据库,那么就会造成线程不安全的问题,Spring是如何解决这个问题的呢?
在Spring项目中Dao层中装配的Connection肯定是线程安全的,其解决方案就是采用ThreadLocal方法,当每个请求线程使用Connection的时候, 都会从ThreadLocal获取一次,如果为null,说明没有进行过数据库连接,连接后存入ThreadLocal中,如此一来,每一个请求线程都保存有一份 自己的Connection。于是便解决了线程安全问题
3 ThreadLocal实战应用
3.1 ehr中的使用
在登录拦截器中将用户信息写入,后续使用时方便取值
3.2 分页插件PageHelper中的应用
3.3 AopContext
4 源码解读
你是否有这样的疑惑?为什么可以直接拿到?对象存放在哪里?存在什么问题?
4.1 get方法
在 get() 方法中也会获取到当前线程的 ThreadLocalMap,如果 ThreadLocalMap 不为 null,则把获取 key 为当前 ThreadLocal 的值;否则调用 setInitialValue() 方法返回初始值,并保存到新创建的 ThreadLocalMap 中。
4.2 set方法
调用set时,直接调用set(T value) 方法中,首先获取当前线程,然后在获取到当前线程的 ThreadLocalMap,如果 ThreadLocalMap 不为 null,则将 value 保存到 ThreadLocalMap 中,并用当前 ThreadLocal 作为 key;否则创建一个 ThreadLocalMap 并给到当前线程,然后保存 value。
ThreadLocalMap 相当于一个 HashMap,是真正保存值的地方
map的set,如果map为空,则创建一个
4.3 initialValue() 方法
initialValue() 是 ThreadLocal 的初始值,默认返回 null,子类可以重写改方法,用于设置 ThreadLocal 的初始值。
4.4 remove() 方法
ThreadLocal 还有一个 remove() 方法,用来移除当前 ThreadLocal 对应的值。同样也是同过当前线程的 ThreadLocalMap 来移除相应的值。
getMap拿到了什么?
在 set,get,initialValue 和 remove 方法中都会获取到当前线程,然后通过当前线程获取到 ThreadLocalMap,如果 ThreadLocalMap 为 null,则会创建一个 ThreadLocalMap,并给到当前线程
此处t是Thread,直接可以“点”拿到这个map
每个Thread对象内部都维护了一个ThreadLocalMap这样一个ThreadLocal的Map,可以存放若干个ThreadLocal
在使用 ThreadLocal 类型变量进行相关操作时,都会通过当前线程获取到 ThreadLocalMap 来完成操作。每个线程的 ThreadLocalMap 是属于线程自己的,ThreadLocalMap 中维护的值也是属于线程自己的。这就保证了 ThreadLocal 类型的变量在每个线程中是独立的,在多线程环境下不会相互影响。
5 使用注意事项
1)有可能导致内存泄漏,使用完毕后,需要remove
在 ThreadLocalMap 的 set(),get() 和 remove() 方法中,都有清除无效 Entry 的操作,这样做是为了降低内存泄漏发生的可能。
Entry 中的 key 使用了弱引用的方式,这样做是为了降低内存泄漏发生的概率,但不能完全避免内存泄漏。
假设 Entry 的 key 没有使用弱引用的方式,而是使用了强引用:由于 ThreadLocalMap 的生命周期和当前线程一样长,那么当引用 ThreadLocal 的对象被回收后,由于 ThreadLocalMap 还持有 ThreadLocal 和对应 value 的强引用,ThreadLocal 和对应的 value 是不会被回收的,这就导致了内存泄漏。所以 Entry 以弱引用的方式避免了 ThreadLocal 没有被回收而导致的内存泄漏,但是此时 value 仍然是无法回收的,依然会导致内存泄漏。
ThreadLocalMap 已经考虑到这种情况,并且有一些防护措施:在调用 ThreadLocal 的 get(),set() 和 remove() 的时候都会清除当前线程 ThreadLocalMap 中所有 key 为 null 的 value。这样可以降低内存泄漏发生的概率。所以我们在使用 ThreadLocal 的时候,每次用完 ThreadLocal 都调用 remove() 方法,清除数据,防止内存泄漏。
2)使用线程池时,父子线程传递慎用,因为初始化时机为线程创建时
3)针对2有什么方案可以解决?
TransmittableThreadLocal
源码地址: https://github.com/alibaba/transmittable-thread-local
详解:https://www.jianshu.com/p/e0774f965aa3
ThreadLocal源码解析及实战应用的更多相关文章
- Java 8 ThreadLocal 源码解析
Java 中的 ThreadLocal是线程内的局部变量, 它为每个线程保存变量的一个副本.ThreadLocal 对象可以在多个线程中共享, 但每个线程只能读写其中自己的副本. 目录: 代码示例 源 ...
- 一文详解RocketMQ-Spring的源码解析与实战
摘要:这篇文章主要介绍 Spring Boot 项目使用 rocketmq-spring SDK 实现消息收发的操作流程,同时笔者会从开发者的角度解读 SDK 的设计逻辑. 本文分享自华为云社区< ...
- ThreadLocal源码解析
主要用途 1)设计线程安全的类 2)存储无需共享的线程信息 设计思路 ThreadLocalMap原理 1)对象存储位置-->当前线程的ThreadLocalMap ThreadLocalMap ...
- 一步一步学多线程-ThreadLocal源码解析
上网查看了很多篇ThreadLocal的原理的博客,上来都是文字一大堆,费劲看了半天,大脑中也没有一个模型,想着要是能够有一张图明确表示出来ThreadLocal的设计该多好,所以就自己看了源码,画了 ...
- ThreadLocal源码解析-Java8
目录 一.ThreadLocal介绍 1.1 ThreadLocal的功能 1.2 ThreadLocal使用示例 二.源码分析-ThreadLocal 2.1 ThreadLocal的类层级关系 2 ...
- Thread、ThreadLocal源码解析
今天来看一下Thread和ThreadLocal类的源码. 一.Thread (1)首先看一下线程的构造方法,之后会说每种参数的用法,而所有的构造函数都会指向init方法 //空构造创建一个线程 Th ...
- ThreadLocal源码解析,内存泄露以及传递性
我想ThreadLocal这东西,大家或多或少都了解过一点,我在接触ThreadLocal的时候,觉得这东西很神奇,在网上看了很多博客,也看了一些书,总觉得有一个坎跨不过去,所以对ThreadLoca ...
- Java ThreadLocal 的使用与源码解析
GitHub Page: http://blog.cloudli.top/posts/Java-ThreadLocal-的使用与源码解析/ ThreadLocal 主要解决的是每个线程绑定自己的值,可 ...
- 实战录 | Kafka-0.10 Consumer源码解析
<实战录>导语 前方高能!请注意本期攻城狮幽默细胞爆表,坐地铁的拉好把手,喝水的就建议暂时先别喝了:)本期分享人为云端卫士大数据工程师韩宝君,将带来Kafka-0.10 Consumer源 ...
- Scala 深入浅出实战经典 第65讲:Scala中隐式转换内幕揭秘、最佳实践及其在Spark中的应用源码解析
王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-87讲)完整视频.PPT.代码下载:百度云盘:http://pan.baidu.com/s/1c0noOt6 ...
随机推荐
- 测试用例又双叒叕失败了,NLP帮你
摘要:本文将介绍如何使用AI技术实现失败测试用例的智能分析. 本文分享自华为云社区<测试用例又双叒叕失败了,啥原因?NLP帮你来分析>,作者: 敏捷的小智 . 随着软件行业的快速发展,为了 ...
- 实践解析可视化开发平台FlinkSever优势
摘要:华为Flink可视化开发平台FlinkServer作为自研服务,能够提供比原生flinksql接口更强的企业级特性,比如任务的集中管理,可视化开发,多数据源配置等. 本文分享自华为云社区< ...
- Solon2 开发之IoC,十、Bean 在容器的两层信息
1.剖析 Bean 的装包与注册过程 比如,用配置器装配一个 Bean (本质是装配出一个 BeanWrap,并自动注册到容器): @Configuration public class Config ...
- SpringBoot Docker 发布
本文是手动模式,可以移步 Intellij IDEA 集成 Docker 发布 使用 Intellij 集成Docker 发布,比较方便 pom 文件 <groupId>com.vipso ...
- Spark面试题(六)——Spark资源调优
Spark系列面试题 Spark面试题(一) Spark面试题(二) Spark面试题(三) Spark面试题(四) Spark面试题(五)--数据倾斜调优 Spark面试题(六)--Spark资源调 ...
- SuperSocket 简单示例
这是一个SuperSocket 简单示例,包括服务端和客户端. 一.首先使用NuGet安装SuperSocket和SuperSocket.Engine 二.实现IRequestInfo(数据包): 数 ...
- #2037:今年暑假不AC
Problem Description "今年暑假不AC?" "是的." "那你干什么呢?" "看世界杯呀,笨蛋!" & ...
- 航拍倾斜摄影 Web 3D GIS 数字孪生智慧火电厂
前言 7 月份,245 个国家气象站日最高气温突破 7 月历史极值:同时,疫情防控形势向好,企业加快复工达产节奏,电力负荷屡创新高.煤电作为我国最主要的电源,用不足 50% 的装机占比,生产了全国约 ...
- vue+spingboot 实现服务器端文件下载功能
vue3 和springboot配合如何实现服务器端文件的下载. 先看springboot的后台代码: @PostMapping("/download") @ResponseBod ...
- uni-app打包到安卓步骤
1.打包到安卓 https://www.bilibili.com/video/BV1BJ411W7pX?p=56 2.打包到IOS https://www.jianshu.com/p/ef6e6e01 ...