之前所学不精,现在看一下确实是,我ThreadLocal里如果都存的是一个共享变量的话,那么肯定是会两边都相同的。其实现在回头看这些代码就没有了当初学术不精时候的疑惑了,反正也被喷了,趁这个被喷的时间索性更正一下ThreadLocal的存储机制

测试代码相当简单

public static void main(String[] args){ThreadLocal<String> tl1 = new ThreadLocal<>();tl1.set("tl1");System.out.println(tl1.get());}

这里要分析的也就两行

  1. ThreadLocal是怎么set的
  2. ThreadLocal是怎么get的

ThreadLocal是怎么set的

我们直接就看ThreadLocal的set方法

    public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);}

这里面有四个点

  1. ThreadLocalMap类是个什么东西
  2. getMap方法是什么
  3. map.set方法是怎么set的
  4. createMap方法是什么,为什么需要线程参数 t

其实这里对于第四个点,我没点进去看也是有点疑惑的,为什么这个createMap方法的两个参数和上一句map.set的两个参数不一样,这不都是set一个键值对么,然后点进去就什么都知道了。(我之所以这么说是因为我觉得总会有人和我想的一样的)

下面逐一解释这4个点

ThreadLocalMap类是个什么东西

ThreadLocalMap是ThreadLocal的一个静态内部类,内部指的看一下的东西如下

  • Entry类,这个类比HashMap里的Entry简单多了,就一个构造,参数一个是Threadlocal键对象,一个Object值对象
  • Entry数组table,做hash存储用的,懂HashMap的我就不说了
  • 再就是阈值啊,初始大小之类的参数,这些在此文章就不关心了

getMap方法是什么​​​​​​​:

getMap(Thread t)方法也是ThreadLocal类的一个内部方法

ThreadLocalMap getMap(Thread t) {return t.threadLocals;}

这方法的意思就是返回线程t的内部参数threadLocals,关于线程对象中threadLocals参数,总结起来就是你用不到ThreadLocal,线程对象的这个属性就一直是null,这一点了解到这里就可以了,有兴趣可以去看Thread类。接着上面的逻辑,如果getMap不是空,就用ThreadLocalMap的set方法置入一个以当前ThreadLocal对象为键,value为值得这么一个键值对;如果getMap为空,那么就以createMap方法set第一个值。

map.set方法是怎么set的​​​​​​​:

ThreadLocalMap类的set方法

private void set(ThreadLocal key, Object value) {// We don't use a fast path as with get() because it is at// least as common to use set() to create new entries as// it is to replace existing ones, in which case, a fast// path would fail more often than not.Entry[] tab = table;int len = tab.length;int i = key.threadLocalHashCode & (len-1);for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {ThreadLocal k = e.get();if (k == key) {e.value = value;return;}if (k == null) {replaceStaleEntry(key, value, i);return;}}tab[i] = new Entry(key, value);int sz = ++size;if (!cleanSomeSlots(i, sz) && sz >= threshold)rehash();}

这里的处理逻辑几乎和HashMap的一样,虽然没HashMap那么细

  • 计算当前键的hash值
  • 去table里找,重复键就替换值,不重复就在该位置添加这个键值对
  • 当前容量超过阈值就扩容然后rehash()

createMap方法是什么,为什么需要线程参数 t​​​​​​​:

关于createMap方法的逻辑

void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);}

其中ThreadLocalMap的构造方法

ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {table = new Entry[INITIAL_CAPACITY];int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);table[i] = new Entry(firstKey, firstValue);size = 1;setThreshold(INITIAL_CAPACITY);}

createMap方法的意思就是,构造一个新的ThreadLocalMap对象,将value对象塞进map,然后把传入的线程对象的threadLocals属性指向这个新ThreadLocalMap。

ThreadLocal是怎么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();}

和HashMap一样,对应的key有就返回value,没有就null

到这里代码就讲解完了

总结(虽然我很想把总结写在开头)

  • ​​​​​​​ThreadLocal进行set的时候,是在当前线程Thread中获取到有且唯一的ThreadLocalMap对象(如果没有就新建一个ThreadLocalMap对象设置进Thread的属性里),然后把自己作为键,value作为值set进这个Map里
  • ThreadLocal进行get的时候,是从当前线程Thread中获取到有且唯一的ThreadLocalMap对象(Thread的ThreadLocalMap属性如果为空,也就是说这个线程从来都没有用过ThreadLocal设置过值,返回null),然后把自己做为键去该Map里面找,找到就返回对于的value,没有就返回null

昨天查资料看到了ThreadLocal这个类,原来一直没有仔细关注过,牛客网看到的一道题说

ThreadLocal用哈希表的形式为每一个线程都提供一个变量的副本

并且给的回答是正确的,这里我们想一下,什么叫变量的副本,如果某一个线程中副本被修改,那么,其他线程中“副本”会不会被修改。

我们来看以下代码:

public class Demo1 {private static ThreadLocal<Student> local = new ThreadLocal<Student>();public static void main(String[] args) {final Student student = new Student();  //所谓的副本原始对象,我们就存这个student.setAge(19);                        //给个初始值19/*** 实验策略是创建两个线程都进行保存student,然后都休息一段时间(给个3秒)* A线程休息完后修改student中的年龄为11* B线程在休息完3秒后继续休息2秒,目的是为了等A修改完* B线程休息完后取出自己所存的Student,看看里面的age到底是19还是11*/new Thread(){              //A线程public void run() {local.set(student);             try {Thread.sleep(3000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}local.get().setAge(11);};}.start();new Thread(){               //B线程public void run() {local.set(student);try {Thread.sleep(5000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println(local.get().getAge());};}.start();}
}
class Student{private int age;public int getAge() {return age;}public void setAge(int age) {this.age = age;}
}

最后结果为11,这个结果也就是说两个线程里面存的是同一个Student对象,修改时线程之间会被影响,而不是所谓的各自一个“副本”,谁也影响不了谁

具体ThreadLocal中是怎么存的,简单来说就是ThreadLocal类有方法调用当前Thread的ThreadMap对象(该对象不是HashMap的子类,但是同样实现了HashMap中的拉链式的结构,并且是Thread的内部类),拿到对象后把自己(ThreadLocal)当键,在里面找有没有已经存在的自己,也就是判断自己是否以前存过东西,存过就替换值,没存过就新开辟地方存值。

对于ThreadLocal的具体源码解析,博主http://blog.csdn.net/wanzaixiaoxinjiayou/article/details/49703135有具体分析。

多个线程ThreadLocal中存的是什么相关推荐

  1. Java Review - 线程池中使用ThreadLocal不当导致的内存泄漏案例源码分析

    文章目录 概述 Why 内存泄露 ? 在线程池中使用ThreadLocal导致的内存泄漏 概述 ThreadLocal的基本使用我们就不赘述了,可以参考 每日一博 - ThreadLocal VS I ...

  2. WeakReference在ThreadLocal中的使用

    1.ThreadLocal定义&作用 定义: ThreadLocal叫做线程本地变量,顾名思义,就是Thread的一个内部变量,这个ThreadLocal是属于某个线程的.就好比是某个人的老婆 ...

  3. ThreadLocal究竟存不存在内存泄漏?

    写ThreadLocal原理的文章太多了,笔者这里不想再分析源码,也不想剖析其实现原理,其实也并不难,就直接说下ThreadLocal的原理吧. 1.ThreadLocal原理简介 假设定义了两个Th ...

  4. 【Java】强软弱虚四种引用,弱引用在ThreadLocal中的应用

    Java中的引用类型 - 强软虚弱 1.强引用(StrongReference) Object strongReference = new Object(); 只要有引用指向它,就不会被回收.当内存空 ...

  5. java.线程池 线程数_如何在线程“ main”中修复异常java.lang.NoClassDefFoundError:Java中的org / slf4j / LoggerFactory...

    java.线程池 线程数 此错误表示您的代码或您在应用程序中使用的任何外部库都在使用SLF4J库 (一个开放源代码日志记录库),但无法找到所需的JAR文件,例如slf4j-api-1.7.2.jar因 ...

  6. 如何在线程“ main”中修复异常java.lang.NoClassDefFoundError:Java中的org / slf4j / LoggerFactory...

    此错误表示您的代码或您在应用程序中使用的任何外部库都在使用SLF4J库 (一个开放源代码日志记录库),但无法找到所需的JAR文件,例如slf4j-api-1.7.2.jar因此它是在线程" ...

  7. ThreadLocal中的3个大坑,内存泄露都是小儿科!

    我在参加Code Review的时候不止一次听到有同学说:我写的这个上下文工具没问题,在线上跑了好久了.其实这种想法是有问题的,ThreadLocal写错难,但是用错就很容易,本文将会详细总结Thre ...

  8. java 父子线程 调用链_ZipKin原理学习--Zipkin多线程及线程池中追踪一致性问题解决...

    在学习Zipkin分布式追踪系统中我们了解到Trace在整个调用链是一致的,在web服务中可以通过在header设置Trace值在不同的服务中进行传递,那样在一个服务内部不同的线程,甚至是线程池中Zi ...

  9. Java线程池线程突然没了_70%人答不全!线程池中的一个线程异常了会被怎么处理?...

    #线程池中的一个线程异常了会被怎么处理? 估计很多人会是以下三点答案(me too): 1.抛异常出来并打印在控制台上 2.其他线程任务不受影响 3.异常线程会被回收 但是这里我先提前说一下以上三点不 ...

最新文章

  1. 整合营销系统推荐乐云seo_做seo优化前需要考虑哪些
  2. 我用 PyTorch 复现了 LeNet-5 神经网络(CIFAR10 数据集篇)!
  3. 公司行为(Corporate Actions)
  4. ffmpeg库音频解码示例
  5. okHttp源码解析------待续
  6. Python基础---循环、条件判断
  7. 【二分图】洛谷P2055假期的宿舍
  8. ESXI洗白安装黑群晖教程,附文件
  9. matlab风玫瑰图,基于Matlab的风玫瑰图绘制
  10. 权限管理系统,可以这么设计
  11. 升级Spring Boot 2.x后RelaxedPropertyResolver不可用的解决方案
  12. 机器学习中的数学——Nesterov Momentum
  13. 作为南方人,python教你怎么样看雪
  14. Java中的equals和==比较
  15. 如何快速搭建公司网站?
  16. HTML下拉菜单怎么做成横向,纯css实现横向下拉导航菜单(可做左侧类目导航)
  17. jsp mysql购物网站a_海来福家具商品交易购物网站的设计(JSP,MySQL)(附答辩记录)
  18. 有道云笔记、石墨笔记、 Effie …采编怎么选?
  19. Vue+SpringBoot+ElementUI实战学生管理系统-10.学生管理模块
  20. 准Z源逆变器模型预测控制Simulink仿真

热门文章

  1. 水漆哪个品牌好?十大品牌水漆排行榜
  2. 电商网站商品详情架构
  3. 东莞市重点培育上市后备科技企业名单(科技局)
  4. Jetson Agx Orin使用最正确的方式安装torch1.11.0,torchvision0.12.0,torchaudio0.11.0保姆级教程
  5. 转行大数据还是人工智能,哪个发展更好
  6. 基于matlab的双目摄像头标定
  7. 滴滴自动驾驶服务上线,程维:道阻且长,行则将至
  8. vcenter server7.0安装
  9. 微信公众号之错误返回码
  10. 动软多数据库链接类实例