ThreadLocal详解,ThreadLocal源码分析,ThreadLocal图解
本文脉路:
概念阐释 ----》 原理图解 ------》 源码分析 ------》 思路整理 ----》 其他补充。
一、概念阐述。
ThreadLocal 是一个为了解决多线程并发场景下的数据安全问题的一个工具类。它可以使得多线程环境下成员变量的使用变得安全。
在使用ThreadLocal的时候,每个线程在ThreadLocal上 set值之后,get到的还是自己set的值。并发情况下,线程之间的存值、取值互不影响。
实际上,ThreadLocal的名字取得并不贴切,如果按照它的功能和作用来命名,应该叫做 ThreadLocalVariable, 即:线程本地变量,下面我们来具体分析。
二、原理图解
首先引入一个例子,我们通常对ThreadLocal的使用就类似于下面的伪代码。
载这个伪代码中,我们只需要调用 set(x) 来设置值,调用get()来获得值。而这个context是一个共享变量。
通常情况下“共享”的变量是不安全的,那么凭什么这个ThreadLocal就是安全的呢?怎么保证数据不会出错?
public class Demo{ ThreadLocal<String> context = new ThreadLocal<>(); public String method1(String param1,String param2){
context.set(...);
//...
// other code...
} public String method2(String param1,String param2 ...){
String value = context.get();
// ...
// other code...
} }
OK,上图说明:
上图中,我们先看红线左边,左边图展示了ThreadLocal的内部大概实现。右边展示了使用的时候,对象在内存中的关系示意图。
通过左边的图可以看到,ThreadLocal 有个内部类——ThreadLocalMap, 其实这个就是存放数据的。 而Thread类具有一个ThreadLocalMap成员变量。
再看红线左边的示例代码和图形展示, 我们可以看到数据是跟着Thread走的,Thread所set的值被拴上了一根绳子,并握在自己手上。所以数据不会串。
如果没看明白,没关系,毕竟还没有看具体代码实现。先把这个图当做开胃菜,下面再来大吃代码。
三、源码分析
3.1.ThreadLocal源码注释。
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID). For example, the class below generates unique identifiers local to each thread. A thread's id is assigned the first time it invokes ThreadId.get() and remains unchanged on subsequent calls. import java.util.concurrent.atomic.AtomicInteger; public class ThreadId {
// Atomic integer containing the next thread ID to be assigned
private static final AtomicInteger nextId = new AtomicInteger(0); // Thread local variable containing each thread's ID
private static final ThreadLocal<Integer> threadId =
new ThreadLocal<Integer>() {
@Override protected Integer initialValue() {
return nextId.getAndIncrement();
}
}; // Returns the current thread's unique ID, assigning it if necessary
public static int get() {
return threadId.get();
}
} Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).
粗略翻译:
这个类提供了线程局部变量。它和其他变量不同之处在于,每条线程访问(通过其get或者set方法)的变量都有其自己的独立变量副本。 例如,下面的类为每条线程生成了唯一的标识。
当第一次调用ThreadId.get()方法的时候,线程的ID便分配了,并且在后续的调用中持续保持不变。 public class ThreadId {
// 原子的interger包含的了下一个要分配的线程ID
private static final AtomicInteger nextId = new AtomicInteger(0); // 线程局部变量包含了每个线程的ID.
private static final ThreadLocal<Integer> threadId =
new ThreadLocal<Integer>() {
@Override protected Integer initialValue() {
return nextId.getAndIncrement();//这里复写了初始化方法,使得每个线程在没有set线程值之前就具有默认值,使得get能返回这个初始化值,并且每个线程返回的初识值一样。
}
}; // Returns the current thread's unique ID, assigning it if necessary
public static int get() {
return threadId.get();
}
} 只要线程还存活并且ThreadLocal实例可以访问,那么每个线程都会持有一个自己对线程局部变量的副本的一个隐式引用。 当一个线程消失之后,它对线程局部变量的所有副本都会受到垃圾回收(处分这个副本有别的引用存在)。
为什么ThreadLocal可以实现每条线程访问操作各自互不相干的值呢,是如何实现的呢,我准备从set,get方法作为入口,看它是如何存取的。
3.2 如何 set 值?
ThreadLocal存值方法即set方法实现如下:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
当某个线程调用ThreadLocal实例的set方法的时候。
第一行 先获得了当前调用set的线程 t 。
第二行 根据线程 t 调用 getMap(t)方法找到当前线程对应的ThreadLocalMap。这个map实际上就是 线程 t 的一个成员变量。从代码实现可以看出:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;//返回当前线程的这个threadLocals变量
}
第三行开始的 if判断中,如果有 ThreadLocalMap , 则 调用 set 方法 将:<当前ThreadLocal实例 ,当前set值 > 组成的键值对插入这个 ThreadLocalMap 。
如果获取不到其对应的ThreadLocalMap则创建。(这里的ThreadLocalMap其实就是一个可以存储键值对的数据结构。下面在分析)
createMap方法代码如下:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
//t 是当前线程,threadLocals是Thread类的一个成员变量,这里创建了一个新的ThreadLocalMap对象赋给这个变量
}
3.3 如何 get 值?
如何get 值? 怎么把大象放进冰箱,就怎么把大象从冰箱中拿出来,对不对?
在看get 代码实现之前,先回忆一下上 set 方法是如何存值的:
1. 取得当前线程。
2. 取得当前线程的局部变量ThreadLocalMap
3. 将 当前 ThreadLocal 实例,当前的“大象” 组成键值对,放到这个 ThreadLocalMap中。
4. 完成。
所以,取值的方法应该是和它相反的,也就是“将第3步中的 存,改为 取”。我们来看看代码是不是这样写的?
取值方法get 实现如下:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
果然,就是我们分析的那样。
3.4思路整理
为了存储一个数据,牵扯到了三个概念: ThreadLocal , ThreadLocalMap ,Thread 。
貌似有点乱,整理一下是这样的:
假定 有一个 ThreadLocal 实例,名字叫做 beautifulGirl 。当前有两个线程 T1,T2 分别想将自己的染色体 Y1, Y2 给 beautifulGirl 保管 (你懂得)
实际上 Y1,Y2被放在了两个ThreadLocalMap实例 中,T1,T2 手中分别捏着一个 各自的map实例 map1,map2,Y1,Y2分别在其中。
当T1 想拿回自己的 Y1的时候,就找到beautifulGirl,让把手伸进map1 将Y1拿出来。 其实就是这个原理。
可以结合这个解释,回过头看看图 和代码就明白了。
看明白的朋友也许要问:你不说这个map内部是个 Entry[] 数组吗?这里T1,只是将自己的Y1放进去了,不需要数组啊。一个对象不就行了。实际上:
有一天,T1,又碰到了一个 ThreadLocal实例,beautifulGirl2 ,T1又兴致大发,调用beautifulGirl2.set(X1)方法将自己的染色体 X1 交给beautifulGirl2保管。于是,T1手中捏着的那个map1便有了两个值。所以,map 才被设计成数组。就是为了让T1,T2可以 把X,Y染色体都送出去保存。就是这样:
3.5.ThreadLocalMap是什么。
通过上面的set方法跟踪,可以发现ThreadLocalMap是ThreadLocal的一个静态内部类。这个类的注释如下:
ThreadLocalMap is a customized hash map suitable only for maintaining thread local values.
No operations are exported outside of the ThreadLocal class.
The class is package private to allow declaration of fields in class Thread.
To help deal with very large and long-lived usages, the hash table entries use
WeakReferences for keys. However, since reference queues are not
used, stale entries are guaranteed to be removed only when the table starts running out of space.
ThreadLocalMap 是一个定制的hash map适用于保持线程本地变量。没有操作方法是在ThreadLocal类之外的发生的。
为了帮助处理非常大和长期存活的 使用情况,哈希表 的entry 采用了弱引用(WeakReferences )来作为KEY,并保证 当哈希表空间快要使用完的时候 就的entry会被删除。
ThreadLocal详解,ThreadLocal源码分析,ThreadLocal图解的更多相关文章
- Android应用AsyncTask处理机制详解及源码分析
1 背景 Android异步处理机制一直都是Android的一个核心,也是应用工程师面试的一个知识点.前面我们分析了Handler异步机制原理(不了解的可以阅读我的<Android异步消息处理机 ...
- Java SPI机制实战详解及源码分析
背景介绍 提起SPI机制,可能很多人不太熟悉,它是由JDK直接提供的,全称为:Service Provider Interface.而在平时的使用过程中也很少遇到,但如果你阅读一些框架的源码时,会发现 ...
- Spring Boot启动命令参数详解及源码分析
使用过Spring Boot,我们都知道通过java -jar可以快速启动Spring Boot项目.同时,也可以通过在执行jar -jar时传递参数来进行配置.本文带大家系统的了解一下Spring ...
- 【转载】Android应用AsyncTask处理机制详解及源码分析
[工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重分享成果] 1 背景 Android异步处理机制一直都是Android的一个核心,也是应用工程师面试的一个 ...
- 线程池底层原理详解与源码分析(补充部分---ScheduledThreadPoolExecutor类分析)
[1]前言 本篇幅是对 线程池底层原理详解与源码分析 的补充,默认你已经看完了上一篇对ThreadPoolExecutor类有了足够的了解. [2]ScheduledThreadPoolExecut ...
- 【转载】Android异步消息处理机制详解及源码分析
PS一句:最终还是选择CSDN来整理发表这几年的知识点,该文章平行迁移到CSDN.因为CSDN也支持MarkDown语法了,牛逼啊! [工匠若水 http://blog.csdn.net/yanbob ...
- SpringMVC异常处理机制详解[附带源码分析]
目录 前言 重要接口和类介绍 HandlerExceptionResolver接口 AbstractHandlerExceptionResolver抽象类 AbstractHandlerMethodE ...
- Hadoop RCFile存储格式详解(源码分析、代码示例)
RCFile RCFile全称Record Columnar File,列式记录文件,是一种类似于SequenceFile的键值对(Key/Value Pairs)数据文件. 关键词:Reco ...
- ArrayList用法详解与源码分析
说明 此文章分两部分,1.ArrayList用法.2.源码分析.先用法后分析是为了以后忘了查阅起来方便-- ArrayList 基本用法 1.创建ArrayList对象 //创建默认容量的数组列表(默 ...
- [转]SpringMVC拦截器详解[附带源码分析]
目录 前言 重要接口及类介绍 源码分析 拦截器的配置 编写自定义的拦截器 总结 前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门blog:ht ...
随机推荐
- Node.js的模块系统
编写稍大一点的程序时一般都会将代码模块化.Node.js提供了一个简单的模块系统.模块既可能是一个文件,也可能是包含一个或多个文件的目录. 模块的创建 如果模块是个文件,一般将代码合理拆分到不同的J ...
- android Gui系统之SurfaceFlinger(1)---SurfaceFlinger概论【转】
转自:https://www.cnblogs.com/deman/p/5584198.html 阅读目录 1.OpenGL & OpenGL ES 2.Android的硬件接口HAL 3.An ...
- Python 编程核心知识体系(REF)
Python 编程核心知识体系: https://woaielf.github.io/2017/06/13/python3-all/ https://woaielf.github.io/page2/
- python3+requests库框架设计04-配置文件
python3配置文件的增删改查等操作可以使用内置的ConfigParser模块,可以自行百度学习,也可以看Python3学习笔记27-ConfigParser模块 配置文件一般存放着环境信息,比如u ...
- 转-OWASP CSRFGuard使用细节
版权声明:不存在一劳永逸的技术 只存在不断学习的人.本文为博主原创文章,未经博主允许不得转载.交流联系QQ:1120121072 https://blog.csdn.net/u013474568/ar ...
- 请求头缺少 'Access-Control-Allow-Origin'
报错: 火狐上运行,出现报错信息.已拦截跨源请求:同源策略禁止读取位于 https://xxxxxxx 的远程资源.(原因:CORS 头缺少 'Access-Control-Allow-Origin' ...
- ES--01
ES概念: 垂直搜索(站内搜索) 什么是全文检索和Lucene? 1 全文检索 倒排索引 2 Lucene 就是一个jar包 里面包含了封装好的各种简历倒排索引 以及进行搜索的代码 包括各种算法 我们 ...
- zabbix3.0.4使用percona-monitoring-plugins插件来监控mysql5.6的详细实现过程
zabbix3.0.4使用percona-monitoring-plugins插件来监控mysql5.6的详细实现过程 因为Zabbix自带的MySQL监控没有提供可以直接使用的Key,所以一般不采用 ...
- java乱码问题解决
1.通过统一的过滤器进行了页面过滤(问题排除) 2.通过debug功能发现页面传到servelet和DAO中文都是OK的,可以说明在web程序端没有问题 问题就可能出现在数据库上面 首先查看数据库编码 ...
- 51nod--1240莫比乌斯函数 (数论)
题目: 1240 莫比乌斯函数 基准时间限制:1 秒 空间限制:131072 KB 分值: 0 难度:基础题 收藏 关注 莫比乌斯函数,由德国数学家和天文学家莫比乌斯提出.梅滕斯(Mertens)首先 ...