本文脉路:

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

一、概念阐述。

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. Linux运行时I/O设备的电源管理框架【转】

    转自:https://www.cnblogs.com/coryxie/archive/2013/03/01/2951243.html 本文介绍Linux运行时I/O设备的电源管理框架.属于Linux内 ...

  2. vscode 的使用笔记

    1.使用vscode 的终端命令 ctrl + ~  打开 vs 的终端 这是使用windows 自带的shell终端, 使用git.bash的shell  在设置里面,找到 terminal.int ...

  3. RocketMQ实战快速入门

    转自:https://www.jianshu.com/p/824066d70da8 一.RocketMQ 是什么      Github 上关于 RocketMQ 的介绍:RcoketMQ 是一款低延 ...

  4. Spring Boot (一): Spring Boot starter自定义

    前些日子在公司接触了spring boot和spring cloud,有感于其大大简化了spring的配置过程,十分方便使用者快速构建项目,而且拥有丰富的starter供开发者使用.但是由于其自动化配 ...

  5. 彻底搞懂字符集编码:ASCII,Unicode 和 UTF-8

    一.ASCII 码 我们知道,计算机内部,所有信息最终都是一个二进制值.每一个二进制位(bit)有0和1两种状态,因此八个二进制位就可以组合出256种状态,这被称为一个字节(byte).也就是说,一个 ...

  6. Codeforces 1097G Vladislav and a Great Legend [树形DP,斯特林数]

    洛谷 Codeforces 这题真是妙的很. 通过看题解,终于知道了\(\sum_n f(n)^k​\)这种东西怎么算. update:经过思考,我对这题有了更深的理解,现将更新内容放在原题解下方. ...

  7. swift 学习- 17 -- 析构器

    // 析构器 只适用与 类类型, 当一个类的实例被释放之前, 析构器会被立即调用, 析构器用关键字 deinit 来标示, 类似于构造器要用 init 来标示 // 析构过程原理 // Swift 会 ...

  8. swift 学习- 11 -- 属性

    // '属性'将值跟特定的类, 结构体或枚举关联, 存储属性常量或变量作为实例的一部分,而计算属性计算(不是存储) 一个值, 计算属性可以用于 类, 结构体, 枚举, 存储属性只能用于 类 和 结构体 ...

  9. CSS3-字体渐变色

    示例:Mauger`s Blog <!DOCTYPE HTML> <html> <head> <meta charset="utf-8"& ...

  10. Oracle 数据库架构

    Oracle 数据库架构 查看数据库在linux系统的安装目录路径情况: [root@localhost ~]# cd /u01/app/oracle/oradata/orcl/ [root@loca ...