本文脉路:

概念阐释 ----》  原理图解  ------》 源码分析 ------》  思路整理  ----》 其他补充。

一、概念阐述。

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图解的更多相关文章

  1. Android应用AsyncTask处理机制详解及源码分析

    1 背景 Android异步处理机制一直都是Android的一个核心,也是应用工程师面试的一个知识点.前面我们分析了Handler异步机制原理(不了解的可以阅读我的<Android异步消息处理机 ...

  2. Java SPI机制实战详解及源码分析

    背景介绍 提起SPI机制,可能很多人不太熟悉,它是由JDK直接提供的,全称为:Service Provider Interface.而在平时的使用过程中也很少遇到,但如果你阅读一些框架的源码时,会发现 ...

  3. Spring Boot启动命令参数详解及源码分析

    使用过Spring Boot,我们都知道通过java -jar可以快速启动Spring Boot项目.同时,也可以通过在执行jar -jar时传递参数来进行配置.本文带大家系统的了解一下Spring ...

  4. 【转载】Android应用AsyncTask处理机制详解及源码分析

    [工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重分享成果] 1 背景 Android异步处理机制一直都是Android的一个核心,也是应用工程师面试的一个 ...

  5. 线程池底层原理详解与源码分析(补充部分---ScheduledThreadPoolExecutor类分析)

    [1]前言 本篇幅是对 线程池底层原理详解与源码分析  的补充,默认你已经看完了上一篇对ThreadPoolExecutor类有了足够的了解. [2]ScheduledThreadPoolExecut ...

  6. 【转载】Android异步消息处理机制详解及源码分析

    PS一句:最终还是选择CSDN来整理发表这几年的知识点,该文章平行迁移到CSDN.因为CSDN也支持MarkDown语法了,牛逼啊! [工匠若水 http://blog.csdn.net/yanbob ...

  7. SpringMVC异常处理机制详解[附带源码分析]

    目录 前言 重要接口和类介绍 HandlerExceptionResolver接口 AbstractHandlerExceptionResolver抽象类 AbstractHandlerMethodE ...

  8. Hadoop RCFile存储格式详解(源码分析、代码示例)

    RCFile   RCFile全称Record Columnar File,列式记录文件,是一种类似于SequenceFile的键值对(Key/Value Pairs)数据文件.   关键词:Reco ...

  9. ArrayList用法详解与源码分析

    说明 此文章分两部分,1.ArrayList用法.2.源码分析.先用法后分析是为了以后忘了查阅起来方便-- ArrayList 基本用法 1.创建ArrayList对象 //创建默认容量的数组列表(默 ...

  10. [转]SpringMVC拦截器详解[附带源码分析]

      目录 前言 重要接口及类介绍 源码分析 拦截器的配置 编写自定义的拦截器 总结 前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门blog:ht ...

随机推荐

  1. NandFlash和iNand【转】

    转自:https://www.cnblogs.com/PengfeiSong/p/6380447.html nand 1.nand的单元组织:block与page(大页Nand与小页Nand)(1)N ...

  2. sort和uniq去重操作【转】

    去除重复行 sort file |uniq   查找非重复行 sort file |uniq -u   查找重复行 sort file |uniq -d   统计 sort file | uniq - ...

  3. 2017.12.10《“剑锋OI”普及组多校联盟系列赛(14)#Sooke#Kornal 的课余时间 》分析报告

    报告内容如下 - - [导语] ------ 太晚了,时间也紧,一切尽量从简吧 PS:本文题目来自剑锋OI 所以废话也不多说,进入正题吧,代码直接跟在题目后边儿,主要分析在代码前,次要的就写在代码后面 ...

  4. 【原创】大数据基础之Airflow(2)生产环境部署airflow研究

    一 官方 airflow官方分布式部署结构图 airflow进程 webserver scheduler flower(非必须) worker airflow缺点 scheduler单点 通过在sch ...

  5. 无锁编程 - Double-checked Locking

    Double-checked Locking,严格意义上来讲不属于无锁范畴,无论什么时候当临界区中的代码仅仅需要加锁一次,同时当其获取锁的时候必须是线程安全的,此时就可以利用 Double-check ...

  6. MQ选型之RabbitMQ

    RabbitMQ是部署最广泛的开源消息代理.[官方原话] 前言: MQ 是什么?队列是什么,MQ 我们可以理解为消息队列(message queue),队列我们可以理解为管道.以管道的方式做消息传递. ...

  7. Codeforces 487E Tourists [广义圆方树,树链剖分,线段树]

    洛谷 Codeforces 思路 首先要莫名其妙地想到圆方树. 建起圆方树后,令方点的权值是双联通分量中的最小值,那么\((u,v)\)的答案就是路径\((u,v)\)上的最小值. 然而这题还有修改, ...

  8. 快速开发android,离不开这10个优秀的开源项目

    作为一名菜鸡Android,时常瞻仰大佬们的开源项目是非常必要的.这里我为大家收集整理了10个优秀的开源项目,方便我们日常开发中学习! 作者:ListenToCode博客:https://www.ji ...

  9. ios 本地存储文件夹的使用注意

    文件夹 tmp 属于临时文件夹,不需要自己删除,系统会在应用退出后清空   文件夹 Library 下面的子文件 Caches 也是用来存储的,,但是Library 基本上不会被清除,但是在内存不足的 ...

  10. Confluence 6 创建站点的导出文件

    希望为你的站点创建一个 XML 导出文件: 进入  > 基本配置(General Configuration) > 备份和恢复(Backup & Restore). 选择 归档到备 ...