ThreadLocal原理简单刨析
ThreadLocal原理简单刨析
ThreadLocal实现了各个线程的数据隔离,要知道数据是如何隔离的,就要从源代码分析。
ThreadLocal原理
需要提前说明的是:ThreadLocal只是一个向线程对象中存取数据的工具,ThreadLocal对象本身并不储存数据。
源码剖析
public class TestThread {
public static void main(String[] args) {
ThreadLocal<String> local = new ThreadLocal<>();
local.set("main's data");
System.out.println("main get = " + local.get());
}
}
/*
main get = main's data
*/
让我们从源码的角度分析,到底发生了什么。
set()原理
// 向当前线程放入数据
public void set(T value) {
Thread t = Thread.currentThread(); // t 就是main线程
ThreadLocalMap map = getMap(t); // 获取当前线程从父类Thread中继承的一个实例变量threadLocals
if (map != null) {
map.set(this, value); // this代表当前的ThreadLocal对象,也就是local
} else {
createMap(t, value); // 创建新的map
}
}
// 获取存放数据的容器ThreadLocalMap, 实际上是Thread对象的一个实例变量。
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
// 实例化ThreadLocalMap对象,并将t线程中的threadLocals实例变量引用到该对象
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
get()原理
public T get() {
Thread t = Thread.currentThread(); // 获取当前的线程,也就是main线程
ThreadLocalMap map = getMap(t); // 获取ThreadLocalMap对象
if (map != null) {
// Entry对象可以理解为键值对
ThreadLocalMap.Entry e = map.getEntry(this); // this就是local,获取以local为键的Entry对象
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value; // 有值就获取值并返回
return result;
}
}
return setInitialValue(); // 返回的值还是null
}
private T setInitialValue() {
T value = initialValue(); // initialValue()返回null, 此时value的值是null
Thread t = Thread.currentThread(); // 此时t是main线程
ThreadLocalMap map = getMap(t); // 获取当前线程从父类Thread中继承的一个实例变量threadLocals
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
if (this instanceof TerminatingThreadLocal) {
TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
}
return value; // 最后value还是null
}
// 返回null
protected T initialValue() {
return null;
}
InheritableThreadLocal
InheritableThreadLocal顾名思义,可以可以达到子线程继承父线程中数据的效果。通过一段代码来看效果。
public class TestThread {
public static void main(String[] args) {
InheritableThreadLocal<String> local = new InheritableThreadLocal<>();
local.set("main's data");
Thread one = new One(local);
one.start();
System.out.println("main get = " + local.get());
}
}
class One extends Thread {
private int value;
public InheritableThreadLocal<String> local;
public One(InheritableThreadLocal<String> local) {
this.local = local;
}
@Override
public void run() {
local.set("one's data");
System.out.println("one get = " + local.get());
}
}
/*
main get = main's data
one get = main's data
*/
父线程使用InheritableThreadLocal和ThreadLocal存数据的区别
InheritableThreadLocal继承了ThreadLocal类。它重写了父类中的三个方法。
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
protected T childValue(T parentValue) {
return parentValue;
}
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
set()发生了什么变化
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t); // 在这里会调用重写了的getmap()方法
if (map != null) {
map.set(this, value);
} else {
createMap(t, value); // 调用了重写了的createMap()方法
}
}
其实只发生了一件事,之前存数据的字段是threadLocals,现在变成了inheritableThreadLocals。
子线程继承父线程数据的过程
继承数据的关键在于子线程对象在实例化的时候会从父线程的threadLocals中拷贝数据。子线程对象在实例化的时候,会先实例化父类,也就是会执行下面的构造函数,,然后顺序执行下列构造器。
1. public One(InheritableThreadLocal<String> local) {
this.local = local;
}
2. public Thread() {
this(null, null, "Thread-" + nextThreadNum(), 0);
}
3. public Thread(ThreadGroup group, Runnable target, String name,
long stackSize) {
this(group, target, name, stackSize, null, true);
}
4. private Thread(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
...
Thread parent = currentThread(); // parent就是main线程
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals = // 这里的this是子线程,也就是one线程
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
...
}
在第三个构造器可以看到传给下一个构造器中inheritableThreadLocals的参数的值是true。
接下来再来看看第四个构造器中ThreadLocal.createInheritableMap()函数是干什么用的,其实就是将父线程中的数据拷贝给子线程。
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
// 本质上就是循环遍历拷贝父线程的数据,然后给子线程
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (Entry e : parentTable) {
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
这样子线程就获取了父线程的中的数据,达到了继承数据的效果。
不过有一点需要需要注意,子线程对象不是引用父线程对象中的数据,而是直接在子线程对象实例化的过程中拷贝了一份过来,所以子线程跑起来之后,父子线程中的数据就不相关了。不过如果存入的数据是引用类型,那么还是会有实时共享的效果。
ThreadLocal原理简单刨析的更多相关文章
- 简析ThreadLocal原理及应用
简析ThreadLocal原理及应用 原创: 东晨雨 JAVA万维猿圈 4月17日 ThreadLocal的源码加上注释不超过八百行,源码结构清晰,代码也比较简洁.ThreadLocal可以说是Jav ...
- Kubernetes(k8s)底层网络原理刨析
目录 1 典型的数据传输流程图 2 3种ip说明 3 Docker0网桥和flannel网络方案 4 Service和DNS 4.1 service 4.2 DNS 5 外部访问集群 5.1 外部访问 ...
- 温故知新-多线程-深入刨析volatile关键词
文章目录 摘要 volatile的作用 volatile如何解决线程可见? CPU Cache CPU Cache & 主内存 缓存一致性协议 volatile如何解决指令重排序? volat ...
- 深入刨析tomcat 之---第8篇 how tomcat works 第11章 11.9应用程序,自定义Filter,及注册
writed by 张艳涛, 标签:全网独一份, 自定义一个Filter 起因:在学习深入刨析tomcat的学习中,第11章,说了调用过滤链的原理,但没有给出实例来,自己经过分析,给出来了一个Filt ...
- Orchard 刨析:Logging
最近事情比较多,有预研的,有目前正在研发的,都是很需要时间的工作,所以导致这周只写了两篇Orchard系列的文章,这边不能保证后期会很频繁的更新该系列,但我会写完这整个系列,包括后面会把正在研发的东西 ...
- Orchard 刨析:Caching
关于Orchard中的Caching组件已经有一些文章做了介绍,为了系列的完整性会再次对Caching组件进行一次介绍. 缓存的使用 在Orchard看到如下一段代码: 可以看到使用缓存的方法Get而 ...
- Orchard 刨析:导航篇
之前承诺过针对Orchard Framework写一个系列.本应该在昨天写下这篇导航篇,不过昨天比较累偷懒的去玩了两盘单机游戏哈哈.下面进入正题. 写在前面 面向读者 之前和本文一再以Orchard ...
- Learning Cocos2d-x for WP8(2)——深入刨析Hello World
原文:Learning Cocos2d-x for WP8(2)--深入刨析Hello World cocos2d-x框架 在兄弟篇Learning Cocos2d-x for XNA(1)——小窥c ...
- java基础解析系列(七)---ThreadLocal原理分析
java基础解析系列(七)---ThreadLocal原理分析 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)-- ...
随机推荐
- 【Web动画】科技感十足的暗黑字符雨动画
本文将使用纯 CSS,带大家一步一步实现一个这样的科幻字符跳动背景动画.类似于这样的字符雨动画: 或者是类似于这样的: 运用在一些类似科技主题的背景之上,非常的添彩. 文字的竖排 首先第一步,就是需要 ...
- 利用swagger和API Version实现api版本控制
场景: 在利用.net core进行api接口开发时,经常会因为需求,要开发实现统一功能的多版本的接口.比如版本V1是给之前用户使用,然后新用户有新需求,这时候可以单独给这个用户写接口,也可以在V1基 ...
- [.NET大牛之路 006] 了解 Roslyn 编译器
.NET大牛之路 • 王亮@精致码农 • 2021.07.09 维基百科对编译器的解释是:编译器是一种程序,它将某种编程语言编写的源代码(原始语言)转换成另一种编程语言(目标语言).编译是从源代码(通 ...
- centos7 下安装docker报错:You could try using...
搞了台VPS,想要装docker,发现死活装不上,各种报错.之前系统是centos6,发现官方现在已经不支持centos6了,遂升级到centos7,然后还是出现下面这个错误. Error: Pack ...
- C++//菱形继承 //俩个派生类继承同一个基类 //又有某个类同时继承俩个派生类 //成为 菱形继承 或者 钻石 继承//+解决
1 //菱形继承 2 //俩个派生类继承同一个基类 3 //又有某个类同时继承俩个派生类 4 //成为 菱形继承 或者 钻石 继承 5 6 #include <iostream> 7 #i ...
- 第一个Java文件
HelloWorld 1.新建一个文件夹,用来存放java文件的 2.用subline来编辑第一个Java文件 要注意的是java的文件名为.java 我们自定义的文件名是Hello 3.编写第一个j ...
- 第3篇-CallStub新栈帧的创建
在前一篇文章 第2篇-JVM虚拟机这样来调用Java主类的main()方法 中我们介绍了在call_helper()函数中通过函数指针的方式调用了一个函数,如下: StubRoutines::cal ...
- 编程熊讲解LeetCode算法《二叉树》
大家好,我是编程熊. 往期我们一起学习了<线性表>相关知识. 本期我们一起学习二叉树,二叉树的问题,大多以递归为基础,根据题目的要求,在递归过程中记录关键信息,进而解决问题. 如果还未学习 ...
- Linux命令(五)之service服务查找、启动/停止等相关操作
.personSunflowerP { background: rgba(51, 153, 0, 0.66); border-bottom: 1px solid rgba(0, 102, 0, 1); ...
- 常见的嵌入式linux学习和如何选择ARM芯片问答
常见的ARM嵌入式学习问答,设计者和学习者最关心的11个问题: 1. ARM嵌入式是学习硬件好还是学习软件好? 2. 嵌入式软件和硬件,哪一种职位待遇更高?或者说, ...