多个线程ThreadLocal中存的是什么

2023-05-16

之前所学不精,现在看一下确实是,我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);
        else
            createMap(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 block
					e.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 block
					e.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有具体分析。

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

多个线程ThreadLocal中存的是什么 的相关文章

  • typora主题更改(以及旧版本下载地址)

    目录 1 Typora官网2 旧版Typora下载地址3 Typora主题商店3 1 找到本地主题文件夹3 2 添加新主题并使用 4 在Typora中使用LaTeX主题 1 Typora官网 官网地址 xff1a https typora
  • 将投影矩阵P利用QR分解分解出摄像机内外参数(Opencv)

    将投影矩阵P利用QR分解分解出摄像机内外参数 xff08 Opencv xff09 将投影矩阵P利用QR分解分解出摄像机内外参数 输入 xff1a P xff1a 投影矩阵 xff0c 3 4 输出 xff1a K xff1a 内参数矩阵
  • (转载)依赖、关联、聚合、组合

    类与类图 1 类 Class 封装了数据和行为 xff0c 是面向对象的重要组成部分 xff0c 它是具有相同属性 操作 关系的对象集合的总称 2 在系统中 xff0c 每个类具有一定的职责 xff0c 职责指的是类所担任的任务 xff0c
  • ubuntu14.0.4升级指定内核以及默认内核启动

    一 xff0c 更新到指定的内核版本 1 首先查看当前的内核版本 xff0c 打开终端在窗口输入以下命令 uname a 2 在ubuntu的终端窗口内搜索可用升级的内核版本 apt cache showpkg linux headers
  • 解决Cannot download “https://github.com/sass/node-sass/releases/download...问题

    因很早做了一个小demo xff0c 并且在其他成熟的电脑上 xff08 node配置好的 xff09 下载依赖包没什么问题 xff0c 最近就在新的电脑上配置好所有东西后 xff0c 去下载这个demo的依赖包 xff0c 就出现了nod
  • 如何阅读 Redis 源码?

    在这篇文章中 xff0c 我将向大家介绍一种我认为比较合理的 Redis 源码阅读顺序 xff0c 希望可以给对 Redis 有兴趣并打算阅读 Redis 源码的朋友带来一点帮助 第 1 步 xff1a 阅读数据结构实现 刚开始阅读 Red
  • C语言DFS和BFS解决迷宫问题

    C语言DFS与BFS 迷宫问题 题目描述 给定一个 N times MN M 方格的迷宫 xff0c 迷宫里有 TT 处障碍 xff0c 障碍处不可通过 在迷宫中移动有上下左右四种方式 xff0c 每次只能移动一个方格 数据保证起点上没有障
  • 2022第9周、第10周总结

    差分 最近看到了一个关于差分的题目 题目描述 给定一个长度为n的数列a1 a2 an xff0c 每次可以选择一个区间 l r xff0c 使得这个区间内的数都加1或者都减1 请问至少需要多少次操作才能使数列中的所有数都相等 xff1f 在
  • 装箱问题(DP)

    题目描述 有一个箱子容量为V xff08 正整数 xff0c 0 xff1c xff1d V xff1c xff1d 20000 xff09 xff0c 同时有n个物品 xff08 0 xff1c n xff1c xff1d 30 xff0
  • 丑数(c语言)

    题目描述 我们把只包含质因子2 3和5的数称作丑数 xff08 Ugly Number xff09 例如6 8都是丑数 xff0c 但14不是 xff0c 因为它包含因子7 习惯上我们把1当做是第一个丑数 输入一个数n xff0c 判断它是
  • 2022/12/30总结

    今日学习了二叉树有关知识 二叉树 二叉树通俗来讲就是一个有俩个指针的链表 他们大多长这个样子 xff1a 这里还有俩个概念了 xff0c 二叉树分为完全二叉树和满二叉树 上面所说的是满二叉树 xff0c 顾名思义就是每个父节点都相应的有俩个
  • 滑动窗口算法

    滑动窗口 滑动窗口有俩种 xff1a 定窗口和不定窗口 滑动窗口说白了就是双指针的运用 定窗口说明是一个特定窗口大小 xff0c 通常用来解决相邻的元素 xff0c 最大值 xff0c 最小值 不定窗口说的是 xff0c 先由右指针去找到第
  • 本学期学习计划

    第7周 学习MySQL 写7个基础题 第8周 熟悉C 43 43 语言 学习maven 写7个基础题 第9周 巩固java和MySQL和maven 写5道中等题 第10周 写好项目框架 写5道中等的题目 第11周 写好项目一些基本功能 刷5
  • 2023/4/2总结

    题解 线段树OR树状数组 Virtual Judge vjudge net 正如这道题目一样 xff0c 我的心情也如此 1 这道题是线段树问题 xff0c 更改学生值即可 xff0c 不需要用到懒惰标记 2 再去按照区间查找即可 xff0
  • 2023/4/27总结

    第一周任务 Virtual Judge vjudge net 1 这道题目穷举即可 最多90次 include lt stdio h gt int getLucky int x int a 10 i n t 61 x max 61 0 mi
  • 2023/5/4总结

    刷题 xff1a 第二周任务 Virtual Judge vjudge net 这一题用到了素筛 然后穷举即可 include lt stdio h gt define Maxsize 500000 int a Maxsize long l
  • Epoll原理解析

    从事服务端开发 xff0c 少不了要接触网络编程 Epoll 作为 Linux 下高性能网络服务器的必备技术至关重要 xff0c Nginx Redis Skynet 和大部分游戏服务器都使用到这一多路复用技术 Epoll 很重要 xff0
  • 2023/5/7总结

    最近还是在项目上 xff1a 主要实现了 xff1a 把头像的数据传给服务器 xff1a 服务器开一个文件夹接收 发送文件是 xff0c 用字节流传递很容易出错 xff0c 我因此坏掉了很多文件 这样写就没有坏掉 xff0c 之前一直是图片
  • 2023/5/9总结

    项目 xff1a 这俩天在看文件分流 xff0c 虽然看的原理是把文件切割 xff0c 传输的时候带着下标值 xff0c 或者在字节头去实现 xff0c 然后在服务器当中结合 但是实现起来遇到了很多问题 xff1a 1 需要另外开辟端口号来
  • putty使用方法,中文教程

    转自 http hi baidu com dba chen blog item ce6a7f54cb6522173b29351e html putty使用方法 xff0c 中文教程 序言 大致内容罗列如下 xff1a 最简单的使用 xff0

随机推荐

  • 用 VNC + Putty 把图形界面带出防火墙

    用 VNC 43 Putty 把图形界面带出防火墙 转自http blog sina com cn s blog 53a2aec8010009b6 html 2007 05 24 18 15 24 转载 分类 xff1a 工作 单位的服务器
  • 配置VNC+PuTTY+SSH Tunnel访问Linux

    转自 http blog 163 com yunlei ma blog static 12720893520098492716722 配置VNC 43 PuTTY 43 SSH Tunnel访问Linux 2009 09 04 21 27
  • 如何在c/c++里输出系统时间

    include lt stdio h gt include lt time h gt void main time t rawtime struct tm timeinfo time amp rawtime timeinfo 61 loca
  • 控制台窗口操作

    用于控制台窗口操作的API函数如下 xff1a GetConsoleScreenBufferInfo 获取控制台窗口信息 GetConsoleTitle 获取控制台窗口标题 ScrollConsoleScreenBuffer 在缓冲区中移动
  • 图像增强?图像复原??

    图像增强的目标是改进图片的质量 xff0c 例如增加对比度 xff0c 去掉模糊和噪声 xff0c 修正几何畸变等 xff1b 图像复原是在假定已知模糊或噪声的模型时 xff0c 试图估计原图像的一种技术 图像增强按所用方法可分成频率域法和
  • SQL SERVER DATETIME 常用日期格式转换

    我们经常出于某种目的需要使用各种各样的日期格式 xff0c 当然我们可以使用字符串操作来构造各种日期格式 但是有现成的函数为什么不用呢 xff1f SQL Server中文版的默认的日期字段datetime格式是yyyy mm dd Thh
  • hadoop学习之自定义对象实现 writeable

    Hadoop虽然 已经实现了一些非常有用的Writable xff0c 如Text IntWritable NullWritable等 xff0c 但有时候需要构造一些更加复杂的结果存入context中 xff0c 使用这些方法可能就不是那
  • C语言宏的用法详解

    1 简介 宏在C语言中是一段有名称的代码片段 无论何时使用到这个宏的时候 xff0c 宏的内容都会被这段代码替换掉 主要有两种宏 xff0c 他们的区别主要是在使用上面 xff0c 一种是在使用时类似于数据对象称为Object like x
  • Linux--day04\05

    知识点和问题 1 Linux组基本介绍2 查看文件的所有者3 创建一个组police 再创建一个用户tom xff0c 将tom放在police中 xff0c 然后使用tom来创建ok txt文件 xff0c 看看情况如何 4 使用root
  • 如何在Ubuntu上运行.run文件

    在Ubuntu上运行 run文件 xff0c 有以下几个步骤 xff1a 1 打开一个终端 ctrl 43 alt 43 t 2 cd 到 run文件所在目录 3 输入 34 chmod 43 x foo run 34 4 输入 34 fo
  • /dev/tty、/dev/ttyS/、/dev/ttyUSB区别

    1 dev tty 当前控制终端Terminal 可以使用命令 ps ax 来查看进程与哪个控制终端相连 xff0c 使用命令 tty 可以查看它 具体对应哪个实际终端设备 2 dev ttyn和 dev console xff08 虚拟
  • 怎么解决你的Segmentation fault (core dumped)问题

    http westsoftware blog 163 com blog static 260941092011460252776 开发一个Linux Unix C C 43 43 程序的时候 xff0c 有时候会出现莫名的core dump
  • 前端生成图表

    http www cnblogs com skiler p 6679828 html 1 常用的前端生成图表的工具HighCharts和echarts 2 具体内容可参考官方文档 xff0c 有一些具体实例 xff0c JS和HTML的代码
  • C语言与C++的区别终于有人说清楚了!

    点击蓝字 关注我们 来源于网络 xff0c 侵删 1 前言 在很大程度上 xff0c C 43 43 是C的超集 xff0c 这意味着一个有效的C程序也是一个有效的C 43 43 程序 C和C 43 43 的主要区别是 xff0c C 43
  • python3,浅谈with的神奇魔法

    在实际的编码过程中 xff0c 有时有一些任务 xff0c 需要事先做一些设置 xff0c 事后做一些清理 xff0c 这时就需要python with出场了 xff0c with能够对这样的需求进行一个比较优雅的处理 xff0c 最常用的
  • archlinux安装配置vnc+openbox

    为什么用openbox xff0c 因为它很小 xff0c 占用资源少 够我用了 我用linux大部分只用命令行界面就够了 图形界面程序用的最多的也就是浏览器了 安装相关软件包 span class token comment 更新下系统
  • 安装vsftpd,并将用户锁定到家目录中,不能切换其他目录

    安装vsftpd span class token function rpm span ivh vsftpd 3 0 2 28 el7 x86 64 rpm 创建用户 span class token function useradd sp
  • c语言——http编程

    HTTP协议简介 超文本传输协议是一种用于分布式 协作式和超媒体信息系统的应用层协议 HTTP是一个客户端终端 xff08 用户 xff09 和服务器端 xff08 网站 xff09 请求和应答的标准 xff08 一般基于TCP xff09
  • 4、【STM32】蜂鸣器/按键实验

    目录 前言 理论学习 一 蜂鸣器简介 二 机械按键简介 三 GPIO配置简介 实践学习 一 设计规划 1 1 实验目标 1 2 硬件资源 二 程序设计 2 1 建立工程文件 2 2 led配置 2 3 beep配置 2 4 key配置 2
  • 多个线程ThreadLocal中存的是什么

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