本文脉路:

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

一、概念阐述。

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. MySQL DROP TABLE操作以及 DROP 大表时的注意事项【转】

    删表 DROP TABLE Syntax DROP [TEMPORARY] TABLE [IF EXISTS] tbl_name [, tbl_name] ... [RESTRICT | CASCAD ...

  2. 设计模式C++学习笔记之十一(Bridge桥梁模式)

      桥梁模式,将抽象部分与它的实现部分分离,使它们都可以独立地变化.实现分离的办法就是增加一个类, 11.1.解释 main(),客户 IProduct,产品接口 CHouse,房子 CIPod,ip ...

  3. mysql导入sqlserver数据库表

    原文:https://zhidao.baidu.com/question/1114325744502691499.html 在Navicat for MySQL 管理器中,创建目标数据库(注意:因为是 ...

  4. Excel 2013 表格自用技巧

    参考 Excel表格的基本操作(精选36个技巧) Excel2013基本用法 关于VLOOKUP函数 目录 快速复制单元格 单元格内强制换行 锁定标题行 查找重复值 万元显示 单元格中显示001 按月 ...

  5. where 和 having区别

    WHERE语句在GROUP BY语句之前:SQL会在分组之前计算WHERE语句. HAVING语句在GROUP BY语句之后:SQL会在分组之后计算HAVING语句.

  6. gnutls-3.5.18 static building for windows

    gnutls-3.5.18 static building for windows Required libraries:1. libnettle 2. gmplib Optional librari ...

  7. ASP.NET如何下载大文件

    关于此代码的几点说明: 1. 将数据分成较小的部分,然后将其移动到输出流以供下载,从而获取这些数据. 2. 根据下载的文件类型来指定 Response.ContentType .(参考OSChina的 ...

  8. Light OJ 1148

    题意: 给你N 个人, 每个人说出有多少人和他一队, 不包括他自己, 输出总人数最少值 思路: 排个序, 按照给的数目把人分为一组,就可以得出最少人数 #include<bits/stdc++. ...

  9. Light OJ 1078

    题意: 给你 N,K 输出 KKKK.....KK能整除 N, 输出 K 的个数, (最小) 基础数学, 取摸运算即可. #include<bits/stdc++.h> using nam ...

  10. linux ln 命令使用参数详解(ln -s 软链接)

    ln是linux中一个非常重要的命令,它的功能是为某一个文件在另外一个位置建立一个同步的链接.当我们需要在不同的目录,用到相同的文件时,我们不需要在每一个需要的目录下都放一个必须相同的文件,我们只要在 ...