ThreadLocal原理简单刨析

ThreadLocal实现了各个线程的数据隔离,要知道数据是如何隔离的,就要从源代码分析。

ThreadLocal原理

需要提前说明的是:ThreadLocal只是一个向线程对象中存取数据的工具,ThreadLocal对象本身并不储存数据。

源码剖析

  1. public class TestThread {
  2. public static void main(String[] args) {
  3. ThreadLocal<String> local = new ThreadLocal<>();
  4. local.set("main's data");
  5. System.out.println("main get = " + local.get());
  6. }
  7. }
  8. /*
  9. main get = main's data
  10. */

让我们从源码的角度分析,到底发生了什么。

set()原理

  1. // 向当前线程放入数据
  2. public void set(T value) {
  3. Thread t = Thread.currentThread(); // t 就是main线程
  4. ThreadLocalMap map = getMap(t); // 获取当前线程从父类Thread中继承的一个实例变量threadLocals
  5. if (map != null) {
  6. map.set(this, value); // this代表当前的ThreadLocal对象,也就是local
  7. } else {
  8. createMap(t, value); // 创建新的map
  9. }
  10. }
  11. // 获取存放数据的容器ThreadLocalMap, 实际上是Thread对象的一个实例变量。
  12. ThreadLocalMap getMap(Thread t) {
  13. return t.threadLocals;
  14. }
  15. // 实例化ThreadLocalMap对象,并将t线程中的threadLocals实例变量引用到该对象
  16. void createMap(Thread t, T firstValue) {
  17. t.threadLocals = new ThreadLocalMap(this, firstValue);
  18. }

get()原理

  1. public T get() {
  2. Thread t = Thread.currentThread(); // 获取当前的线程,也就是main线程
  3. ThreadLocalMap map = getMap(t); // 获取ThreadLocalMap对象
  4. if (map != null) {
  5. // Entry对象可以理解为键值对
  6. ThreadLocalMap.Entry e = map.getEntry(this); // this就是local,获取以local为键的Entry对象
  7. if (e != null) {
  8. @SuppressWarnings("unchecked")
  9. T result = (T)e.value; // 有值就获取值并返回
  10. return result;
  11. }
  12. }
  13. return setInitialValue(); // 返回的值还是null
  14. }
  15. private T setInitialValue() {
  16. T value = initialValue(); // initialValue()返回null, 此时value的值是null
  17. Thread t = Thread.currentThread(); // 此时t是main线程
  18. ThreadLocalMap map = getMap(t); // 获取当前线程从父类Thread中继承的一个实例变量threadLocals
  19. if (map != null) {
  20. map.set(this, value);
  21. } else {
  22. createMap(t, value);
  23. }
  24. if (this instanceof TerminatingThreadLocal) {
  25. TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
  26. }
  27. return value; // 最后value还是null
  28. }
  29. // 返回null
  30. protected T initialValue() {
  31. return null;
  32. }

InheritableThreadLocal

InheritableThreadLocal顾名思义,可以可以达到子线程继承父线程中数据的效果。通过一段代码来看效果。

  1. public class TestThread {
  2. public static void main(String[] args) {
  3. InheritableThreadLocal<String> local = new InheritableThreadLocal<>();
  4. local.set("main's data");
  5. Thread one = new One(local);
  6. one.start();
  7. System.out.println("main get = " + local.get());
  8. }
  9. }
  10. class One extends Thread {
  11. private int value;
  12. public InheritableThreadLocal<String> local;
  13. public One(InheritableThreadLocal<String> local) {
  14. this.local = local;
  15. }
  16. @Override
  17. public void run() {
  18. local.set("one's data");
  19. System.out.println("one get = " + local.get());
  20. }
  21. }
  22. /*
  23. main get = main's data
  24. one get = main's data
  25. */

父线程使用InheritableThreadLocal和ThreadLocal存数据的区别

InheritableThreadLocal继承了ThreadLocal类。它重写了父类中的三个方法。

  1. public class InheritableThreadLocal<T> extends ThreadLocal<T> {
  2. protected T childValue(T parentValue) {
  3. return parentValue;
  4. }
  5. ThreadLocalMap getMap(Thread t) {
  6. return t.inheritableThreadLocals;
  7. }
  8. void createMap(Thread t, T firstValue) {
  9. t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
  10. }
  11. }

set()发生了什么变化

  1. public void set(T value) {
  2. Thread t = Thread.currentThread();
  3. ThreadLocalMap map = getMap(t); // 在这里会调用重写了的getmap()方法
  4. if (map != null) {
  5. map.set(this, value);
  6. } else {
  7. createMap(t, value); // 调用了重写了的createMap()方法
  8. }
  9. }

其实只发生了一件事,之前存数据的字段是threadLocals,现在变成了inheritableThreadLocals。

子线程继承父线程数据的过程

继承数据的关键在于子线程对象在实例化的时候会从父线程的threadLocals中拷贝数据。子线程对象在实例化的时候,会先实例化父类,也就是会执行下面的构造函数,,然后顺序执行下列构造器。

  1. 1. public One(InheritableThreadLocal<String> local) {
  2. this.local = local;
  3. }
  4. 2. public Thread() {
  5. this(null, null, "Thread-" + nextThreadNum(), 0);
  6. }
  7. 3. public Thread(ThreadGroup group, Runnable target, String name,
  8. long stackSize) {
  9. this(group, target, name, stackSize, null, true);
  10. }
  11. 4. private Thread(ThreadGroup g, Runnable target, String name,
  12. long stackSize, AccessControlContext acc,
  13. boolean inheritThreadLocals) {
  14. ...
  15. Thread parent = currentThread(); // parent就是main线程
  16. if (inheritThreadLocals && parent.inheritableThreadLocals != null)
  17. this.inheritableThreadLocals = // 这里的this是子线程,也就是one线程
  18. ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
  19. ...
  20. }

在第三个构造器可以看到传给下一个构造器中inheritableThreadLocals的参数的值是true。

接下来再来看看第四个构造器中ThreadLocal.createInheritableMap()函数是干什么用的,其实就是将父线程中的数据拷贝给子线程。

  1. static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
  2. return new ThreadLocalMap(parentMap);
  3. }
  4. // 本质上就是循环遍历拷贝父线程的数据,然后给子线程
  5. private ThreadLocalMap(ThreadLocalMap parentMap) {
  6. Entry[] parentTable = parentMap.table;
  7. int len = parentTable.length;
  8. setThreshold(len);
  9. table = new Entry[len];
  10. for (Entry e : parentTable) {
  11. if (e != null) {
  12. @SuppressWarnings("unchecked")
  13. ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
  14. if (key != null) {
  15. Object value = key.childValue(e.value);
  16. Entry c = new Entry(key, value);
  17. int h = key.threadLocalHashCode & (len - 1);
  18. while (table[h] != null)
  19. h = nextIndex(h, len);
  20. table[h] = c;
  21. size++;
  22. }
  23. }
  24. }
  25. }

这样子线程就获取了父线程的中的数据,达到了继承数据的效果。

不过有一点需要需要注意,子线程对象不是引用父线程对象中的数据,而是直接在子线程对象实例化的过程中拷贝了一份过来,所以子线程跑起来之后,父子线程中的数据就不相关了。不过如果存入的数据是引用类型,那么还是会有实时共享的效果。

ThreadLocal原理简单刨析的更多相关文章

  1. 简析ThreadLocal原理及应用

    简析ThreadLocal原理及应用 原创: 东晨雨 JAVA万维猿圈 4月17日 ThreadLocal的源码加上注释不超过八百行,源码结构清晰,代码也比较简洁.ThreadLocal可以说是Jav ...

  2. Kubernetes(k8s)底层网络原理刨析

    目录 1 典型的数据传输流程图 2 3种ip说明 3 Docker0网桥和flannel网络方案 4 Service和DNS 4.1 service 4.2 DNS 5 外部访问集群 5.1 外部访问 ...

  3. 温故知新-多线程-深入刨析volatile关键词

    文章目录 摘要 volatile的作用 volatile如何解决线程可见? CPU Cache CPU Cache & 主内存 缓存一致性协议 volatile如何解决指令重排序? volat ...

  4. 深入刨析tomcat 之---第8篇 how tomcat works 第11章 11.9应用程序,自定义Filter,及注册

    writed by 张艳涛, 标签:全网独一份, 自定义一个Filter 起因:在学习深入刨析tomcat的学习中,第11章,说了调用过滤链的原理,但没有给出实例来,自己经过分析,给出来了一个Filt ...

  5. Orchard 刨析:Logging

    最近事情比较多,有预研的,有目前正在研发的,都是很需要时间的工作,所以导致这周只写了两篇Orchard系列的文章,这边不能保证后期会很频繁的更新该系列,但我会写完这整个系列,包括后面会把正在研发的东西 ...

  6. Orchard 刨析:Caching

    关于Orchard中的Caching组件已经有一些文章做了介绍,为了系列的完整性会再次对Caching组件进行一次介绍. 缓存的使用 在Orchard看到如下一段代码: 可以看到使用缓存的方法Get而 ...

  7. Orchard 刨析:导航篇

    之前承诺过针对Orchard Framework写一个系列.本应该在昨天写下这篇导航篇,不过昨天比较累偷懒的去玩了两盘单机游戏哈哈.下面进入正题. 写在前面 面向读者 之前和本文一再以Orchard ...

  8. 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 ...

  9. java基础解析系列(七)---ThreadLocal原理分析

    java基础解析系列(七)---ThreadLocal原理分析 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)-- ...

随机推荐

  1. Dubbo 实现一个Route Factory(用于灰度发布)

    Dubbo 可以实现的扩展很多, 官方文档在这: https://dubbo.apache.org/zh/docs/v2.7/dev/impls/ (太简单了....) 下面我们实现一个Route F ...

  2. mitmproxy第一次尝试-猿人学第九题

    启动 mitmdump -s http_proxy.py -p 9000 替换js代码 # -*- coding: utf-8 -*- import re main_url = 'http://mat ...

  3. 2020年!最全Android大厂面试真题合集(附答案)

    这份Android面试真题涵盖了图片,网络和安全机制,网络,数据库,插件化.模块化.组件化.热修复.增量更新.Gradle,架构设计和设计模式,Android Framework .Android优秀 ...

  4. 如何远程调试自定义开发的Flume应用

    一.前言 Flume作为当下最流行的大数据采集组件之一.其本身拥有分布式/高可靠/高可用等优点,但相比较于Flink/Spark/Kafka等大数据组件,其对于本地调试的功能支持度并不高,如果我们没有 ...

  5. MySQL 不完全入门指南

    由于 MySQL 的整个体系太过于庞大,文章的篇幅有限,不能够完全的覆盖所有的方面.所以我会尽可能的从更加贴进我们日常使用的方式来进行解释. 小白眼中的 MySQL 首先,对于我们来说,MySQL 是 ...

  6. Golang语言系列-02-常用数据类型

    Go语言常用数据类型 Go 语言中有丰富的数据类型,除了基本的整型.浮点型.布尔型.字符串.byte/rune 之外, 还有数组.切片.函数.map.通道(channel).结构体等. Go语言的基本 ...

  7. Git (10)-- 打标签(git tag)

    @ 目录 1.列出标签 2.创建标签 2.1.附注标签 2.2.轻量标签 3.后期打标签 4.共享标签 5.删除标签 6.检出标签 超详细 Git 图文版小白教程(持续更新) 像其他版本控制系统(VC ...

  8. STM32—驱动GY85-IMU模块

    GY85是一个惯性测量模块,内部集成了三轴加速度计.三轴陀螺仪.电子罗盘.气压传感器等芯片,用于测量和报告设备速度.方向.重力,模块可以将加速度计.陀螺仪.电子罗盘等传感器的数据进行综合,在上位机可以 ...

  9. 使用msp432搭建的平衡小车(一)

    1.前言 笔者是一名大二学生曾经荒废一年学业,现在不断学习,所以有任何问题都希望讨论提出,你们的支持就是我的动力. 关于硬件搭建的步骤,笔者就不提网上方案太多了,笔者使用编码器电机,驱动采用tb661 ...

  10. 【Openxml】将Openxml的椭圆弧线arcTo转为Svg的椭圆弧线

    本文将介绍如何将OpenXml的actTo转为Svg的弧线(a) OpenXml的artTo 首先下面是一段OpenXml的arcTo弧线 <arcTo wR="152403" ...