ThreadLocal 的工作原理

2023-10-27

ThreadLocal  是线程的内部数据的存储类,通过它可以在指定线程中存储数据,数据存储以后只有在指定的线程中才能获取到对应的存储的数据,其他线程无法获取到数据。

下面通过一个小块代码来分析一下它的使用:

private ThreadLocal<Boolean> mBooleanThread = new ThreadLocal<Boolean>();

...

mBooleanThread.set(true);
Log.e("TAG","main thread:"+mBooleanThread.get());

new Thread("thread #1"){
    @Override
    public void run() {
        mBooleanThread.set(false);
        Log.e("TAG","thread #1:"+mBooleanThread.get());
    }
}.start();
new Thread("thread #2"){
    @Override
    public void run() {
        Log.e("TAG","thread #2:"+mBooleanThread.get());
    }
}.start();

在上面的代码中,在主线程中设置 mBooleanThread.set(true);,在子线程1 中设置了 mBooleanThread.set(false); 在子线程2 中不去设置 mBooleanThread 的值。根据我们开文的描述,只能在指定的线程中获取到指定设置的值。那么结果应该是  main thread:true

thread #1:false

thread #2:null

最终运行结果如下:

12-04 21:50:40.583 4863-4863/com.zx.drawable E/TAG: main thread:true
12-04 21:50:40.585 4863-4881/com.zx.drawable E/TAG: thread #1:false
12-04 21:50:40.586 4863-4882/com.zx.drawable E/TAG: thread #2:null

从日志可以看出,虽然我们访问的是同一个ThreadLocal 对象,但是访问它们的值的时候却可以得到不同的值,这就是ThreadLocal 的神奇之处。ThreadLocal 是 一个泛型类,我们只需要搞清楚它的get 和 set 方法就可以明白了 。

首先来看ThreadLocal 的 set 方法,如下:

    /**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

上面的set 方法中首先会通过getMap 来获取当前线程的 ThredLocalMap 对象。

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

接着在往我们的map中 set 我们存储的泛型 对象 value ,如果map为null,那么就执行下面的操作

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

通过这个代码来创建我们当前线程的threadLocal 对象。

具体的 set 算法如下:


/**
 * Set the value associated with key.
 *
 * @param key the thread local object
 * @param value the value to be 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();
}

   上面的代码实现了数据的存储过程,这里不去具体分析算法。总之通过这个算法可将我们的数据存储在对应线程所在的  Entry[] 的对象中,并且保存当前线程可以访问。

    因为不同的程访问同一个ThreadLocal 的get 方法,在get 方法中会获取到当前线程,然后 根据当前线程获取一个map , 再从对应这个线程的map 中获取到我们存储的数值。

 /**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

 明白了set 的过程,起始这里的get 也会相对好理解一些。通过当前的线程拿到存储数据的map,在从map 中获取到当前线程的  Entry[]对象,从这个对象中取出我们set 给它的值。

    如果map 是 空的话就初始化存储数据的map,并且返回 默认 对象 null

private T setInitialValue() {
	T value = initialValue();
	Thread t = Thread.currentThread();
	ThreadLocalMap map = getMap(t);
	if (map != null)
		map.set(this, value);
	else
		createMap(t, value);
	return value;
}

 默认的value 为null

protected T initialValue() {
   return null;
}

 从ThreadLocal 的set 和 get 方法可以看到,他们的操作都的针对当前线程的 ThreadLocalMap 进行的操作,因此不同的线程中访问同一个ThreadLocal 的 set 和 get 方法,他们针对ThreadLocal 所做的操作仅限于各自线程的内部。这就是为什么ThreadLocal 可以在不同线程中互不干扰的存取数据了。

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

ThreadLocal 的工作原理 的相关文章

  • Android gradle 无法找到方法 'org.grade.api.internal.file.DefaultSourceDirectorySet'

    我尝试将 java 类转换为 Kotlin 除了出现以下错误之外 它工作正常 我尝试重新下载下面建议的所有依赖项 但无济于事 请帮助我解决此错误 Error Error 2 0 Unable to find method org gradl
  • Java 中 float 和 double 基元的比较

    我发现了Java的一个奇怪的角落 我觉得很奇怪 double dd 3 5 float ff 3 5f System out println dd ff 输出 正确 double dd 3 2 float ff 3 2f System ou
  • 空手道弹簧整合

    我们有一个 Spring MVC 应用程序 是否可以在 Karate 中集成 spring 上下文 假设通过 Karate runner 绑定 SpringJunitRunner 或者通过 spring 上下文查找 bean 在空手道中一切
  • IBM Websphere JPA 配置 - 如何更新 persistence.xml

    我是 EJB 3 和 JPA 的新手 我在应用程序服务器中创建了一个数据源 它是jdbc AppDataSource 默认持久性提供程序保留为com ibm websphere persistence PersistenceProvider
  • ScrollView 截掉顶部并在底部留出空间

    当我启动模拟器并进入使用此代码的屏幕时 它显示了大部分文本信息 但切断了屏幕顶部 无法向上滚动 但在底部留下了一些空间 这是代码
  • 配置 Restlet 以在 Google App Engine 上返回 JSP?

    我开发了一个 Restlet 应用程序 我想通过 Restlet 返回一个 URL 请求的 JSP 文件 在不使用重定向的情况下如何实现此目的 IE 假设我在 mydomain com 上有文件 contact jsp 并且我希望人们能够访
  • 查找附近应用程序的用户(iPhone 和 Android)

    我正在开发一个有 iPhone 版本和 Android 版本的应用程序 我的目标是 显示我的应用程序的附近用户列表 iPhone 应用程序用户和 Android 应用程序用户 假设列出当前在 1 英里或 2 英里半径内并且当前正在使用我的应
  • JSF中直接更改URL来限制用户访问页面

    我的应用程序中有两种用户 客户和卖家 我正在使用一个PhaseListener在JSF中防止用户在未登录的情况下访问页面 但在登录后我不知道如何防止用户更改地址栏中的URL并访问他也不允许的页面 例如 阻止客户访问卖家页面 有谁知道我如何防
  • 以编程方式设置谷歌地图片段可见性(API2)

    xml
  • Android Multipart HTTP Post 不发送文件的 MIME 类型

    试图找出我的编码有什么问题 我关注了一篇博客文章here http evgenyg wordpress com 2010 05 01 uploading files multipart post apache 我设法获得了将文件实际上传到
  • 在Java中执行.lnk文件

    我需要在java中执行 lnk文件 指向exe文件的lnk文件 我能怎么做 在 VB net 中我做 Process Start path 它有效 谢谢你的帮助 Use a 流程构建器 http download oracle com ja
  • android recycler 视图适配器、viewpagers、数据库、位图和内存不足错误

    hello im loading lots of little images ex 180x180 10 21kb from a LOCAL database into lots of different recycler views in
  • 需要从 GCM 迁移到 FCM?

    Google 刚刚宣布 Google Cloud Messaging 现已更名为 Firebase Cloud Messaging 这对于 GCM 的现有用户意味着什么以及需要进行哪些迁移 如果有 什么是 FCM 我们为什么要使用它 FCM
  • 标记聚类在 Android 中不显示标记?

    我在 Android 的 PagerFragment 中设置了 MapView 我按照 Google 示例进行操作 但使用 V2 API 设置带有群集标记的地图 它向我显示了一张没有标记的黑色地图 我哪里做错了 import android
  • Java 中的 C#“is”运算符替代方案 [重复]

    这个问题在这里已经有答案了 在 C 中 当我想知道一个对象是否是特定类型的实例时 我可以使用 is 运算符 String foo hi if foo is String 我怎样才能在java中做到这一点 我知道我可以使用 try 语句 还有
  • 如何压缩和解压文件?

    如何压缩和解压 DDMS 中已有的文件 data data mypackage files 我需要一个简单的例子 我已经搜索过与 zip 和 unzip 相关的内容 但是 没有一个例子可供我参考 谁能举个例子 提前谢谢 查看 zip 功能的
  • 如何从扩展 Activity 的类启动扩展片段的意图?

    我在启动时遇到问题intent延伸Fragment 来自一个扩展的类Activity TabBar2 class gt 扩展 Activity Favourite class gt 扩展 Fragment 这就是我写我的意图的方式 Inte
  • 创建带有圆角的 ImageView [重复]

    这个问题在这里已经有答案了 我将如何创建一个ImageView有边框和圆角吗 我想要的角落ImageView为了是圆形的 我尝试创建一个可绘制的形状 其中包含笔划和圆角作为可绘制的背景ImageView但这并没有给出正确的效果 因为图像被填
  • 我可以以编程方式更改 Xamarin.Forms 中的 styles.xml 吗?

    我们有一个可自定义颜色的应用程序 这使得列表视图中所选项目的橙色 Android 默认值有时看起来很糟糕 我们想要更改列表视图所选项目的颜色 我知道如何在我们页面的后台代码 xaml cs 中执行此操作 并且我知道您可以在 styles x
  • Android 中自定义对话框内的日期选择器

    我想在自定义对话框中使用日期选择器 单击按钮上的日历将打开以供用户选择日期 我的 customDilaog 类中有 Button 在该按钮上单击 我想打开日历视图 如果单击此按钮 我的应用程序将崩溃 我已经完成了这个 CustomDialo

随机推荐

  • C语言结构体,共用体所占字节数计算

    字节数 环境 char short int long long long float double 指针 windows 32 gcc 1 2 4 4 8 4 8 4 windows 64 gcc 1 2 4 4 8 4 8 8 无符号型在
  • Android:这是一份全面 & 详细的Webview使用攻略

    前言 现在很多App里都内置了Web网页 Hybrid App 比如说很多电商平台 淘宝 京东 聚划算等等 如下图 京东首页 那么这种该如何实现呢 其实这是Android里一个叫WebView组件实现 今天 我将献上一份全面介绍 WebVi
  • Android Intents和Intent过滤器

    点击打开链接 intent组成 组件名 action动作 data数据 category分类 extras附加信息 flag标记 Intent负责对应用中一次操作的动作 动作涉及数据 附加数据进行描述 Android则根据此Intent的描
  • html2canvas的ignoreElements的使用方法

    我的处女作 Canvas系列教程 在我的Github上正在连载更新 希望能得到您的关注和支持 让我有更多的动力进行创作 教程介绍 教程目录等能在README里查阅 传送门 https github com 827652549 CanvasS
  • unity背景设置透明,qnx层 透传安卓显示

    相机设置为solid color 背景颜色设置为黑色透明度为0 一定设置成黑色 设置成其他颜色打包出来和ios背景融合出很奇怪的颜色 player setting 的render over native ui 勾选 上
  • 事件推送网关:让cmdb告别“花瓶”

    前言 众所周知cmdb在运维过程中的重要性 但是我们不希望它是一个 花瓶 因此 运维思索 cmdb打通zabbix jumpserver探索 成了我们当前面临的一个课题 而我们可以借助cmdb的事件推送来解决此问题 因此引入了事件推送网关
  • 成功解决Python导入opencv报错“DLL load failed while importing cv2: 找不到指定的模”

    场景描述 本人使用腾讯云服务器做图像处理的项目 导入opencv时报错 报错内容 DLL load failed while importing cv2 找不到指定的模 前提 已经安装opencv 个人认为安装教程比较好的博客是这一篇ope
  • npm link详解

    前言 npm install 可以把发布在 npmjs 平台上的模块包下载到本地 npm install g 可以把包下下来的同时 还帮我们配置好全局变量 让我们可以直接使用命令而不是通过 node 来执行或者配置 package json
  • 大气传输、大气辐射传输

    大气传输 大气传输理论是指研究红外辐射和大气相互作用的理论 包括吸收 散射 折射和湍流的影响情况 大气特性 压力 温度 密度及各种成分的含量 随时间 地点以一种极为复杂的方式变化 因此辐射在大气中传输受到的影响也是相当复杂的 定义大气传输理
  • IntelliJ IDEA远程调试:使用IDEA Remote Debug进行高效调试的指南

    引言 在开发分布式系统时 调试是一个重要但复杂的环节 开发者通常需要跨越多个服务 模块和线程来追踪和解决问题 在没有远程调试的情况下 许多开发者会在代码中添加各种日志语句 然后重新部署和上线来调试 这种方法不仅费时 而且可能引入额外的错误或
  • 计算机网络笔记第四章传输层

    四 传输层 1 传输层服务 1 服务和协议 服务和协议的目的是让不同主机之间应用进程之间的逻辑通信 传输协议 TCP UDP 运行于终端系统 可能使用多个协议 发送端 应用层报文划分成分段 分解 向下交互给网络层 接收端 重组成报文 向上交
  • [苹果开发者账号]08 苹果App Store定价机制升级引起的问题

    目录 1 问题 2 排查 3 原因 1 问题 周一的时候 突然运营部门的同事 通过工单向开发部门反应 苹果手机用户使用我们的app产品付款时 有多付款的现象 1098元却付款成了1198元 额外多付款了100元 2 排查 app产品因为是虚
  • Git操作

    目录 Git简介 git使用 流程 核心总结 安装 基础命令 编辑 查看仓库状态 工作区操作 工作区内容提交到暂存区 提交暂存区内容到版本库 git log 和git reflog的区别 git reset hard mix soft的区别
  • 华为OD机试 - 转骰子(Java)

    题目描述 骰子是一个立方体 每个面一个数字 初始为左1 右2 前3 观察者方向 后4 上5 下6 用123456表示这个状态 放置在平面上 可以向左翻转 用L表示向左翻转1次 可以向右翻转 用R表示向右翻转1次 可以向前翻转 用F表示向前翻
  • (2023)ConKI: Contrastive Knowledge Injection for Multimodal Sentiment Analysis

    一 整体部分 话不多说 先上整体框架图 同样的和大多数做法一样 对于文本模态 用Bert提取 音频和视频使用transformer提取特征 这里创新的一点是 通过知识注入适配器Adapter将外部知识注入到每个模态 称为特定知识 再和特征提
  • SQLServer数据库 附加数据库时出错;有关详细信息,请单击“消息”列中的超链接的处理方式

    在SQL Server 数据库中附加数据库时出错 这是由于权限的问题 找到数据库所在文件或文件件 我的数据库文件放到了 新建文件夹 2 中了 所以 我设置下这个文件夹的权限 1 点击右键 选中属性 2 点击安全 编辑 添加 高级 立即查找
  • 将hexo项目部署到github

    个人博客预览点击这里 搭建以及优化hexo项目点击这里 一 在github新建一个仓库 二 安装插件来实现一键部署 三 在项目根目录下的 config yml配置文件末尾做如下修改 四 推送到github仓库 一 在github新建一个仓库
  • linux下jps命令找不到的问题 bash: jps: 未找到命令... --- 记录

    jps是用来查看进程的命令 常常要用jps来查看名称服务是否已启动 但是某些机器报错如下 bash jps command not found 解决办法如下 一 检查基础配置 which Java java version 如果指向的是1
  • MySQL 事务 学习

    MySQL 事务 学习 事务 事务 ACID原则 原子性 一致性 隔离性 持久性 原子性 Atomicity 要么都成功 要么都失败 一致性 Consistency 事务前后的数据完整性要保持一致 隔离性 Isolation 每个事务之间相
  • ThreadLocal 的工作原理

    ThreadLocal 是线程的内部数据的存储类 通过它可以在指定线程中存储数据 数据存储以后只有在指定的线程中才能获取到对应的存储的数据 其他线程无法获取到数据 下面通过一个小块代码来分析一下它的使用 private ThreadLoca