ThreadLocal,看我就够了!

2023-10-31

ThreadLocal


开胃菜

 研究过Handler的应该对ThreadLocal比较眼熟的,线程中的Handler对象就是通过ThreadLocal来存放的。初识ThreadLocal的可能被它的名字有所误导,ThreadLocal初一看可能会觉得这是某种线程实现,而实际并非如此。事实上,它是一个全局变量,用来存储对应Thread的本地变量,这也是为什么将其称之为Local。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
 比如Handler,当我们在一个线程中创建了一个Handler时,在调用Looper.prepare()时通过ThreadLocal保存了当前线程下的Looper对象,而所有线程的Looper都由一个ThreadLocal来维护,也就是在所有线程中创建的Looper都存放在了一个ThreadLocal中,然后创建Handler将Handler与当前线程Looper关联,当调用Looper.loop()的时候通过myLooper()得到的就是当前线程的Looper,当在其他线程使用Handler来发送消息的时候,其实也就是将对应的Message存储到了对应Handler的MessageQueue中,当Looper去分发消息的时候,就是将当前线程中的Looper对应的MessageQueue中的Message通过Handler的回调返回给了Handler所在的线程。如对Handler不了解的,可参考Handler全面解读
这里写图片描述


ThreadLocal模拟

 那么,如何来做到区分不同线程中的变量呢?我们这里模拟一个实现ThreadLocal功能的类,原理大致一样。

public class ThrealLocalImitation<T> {
    private static Map<Thread, Object> sSaveValues = new HashMap<Thread, Object>();

    public synchronized void set(T threadData) {
        Thread thread = Thread.currentThread();
        mSaveValues.put(thread, threadData);
    }

    public synchronized T get() {
        Thread thread = Thread.currentThread();
        return (T) mSaveValues.get(thread);
    }
}

 如上ThreadLocal模仿类里面,通过全局的Map变量sSaveValues,以Thread为key,value为对应线程需要保存的变量,实现了,每个线程对应保存了一个变量,在不同的线程存储不同的变量,通过get方法就能取回对应的值。


ThreadLocal原理分析

 ThreadLocal类通过set、get方法来分别存取变量的,搞懂了这两个方法的功能也就明白ThreadLocal的原理了,所以重点分析这两个方法。

在进入正式的分析之前先来看一个类——ThreadLocalMap

 和我们模拟的ThreadLocal稍有所区别,ThreadLocal不是直接通过一个Map来存储Thread和value对应关系的。
 在Thread类中,有一个变量ThreadLocal.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);
        }

    static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

 ThreadLocalMap内部维护一个数组Entry[] table, Entry对应存储了key–value(ThreadLocal—value)。ThreadLocalMap实际上是一个实现了自定义的寻址方式的HashMap。
 那么ThreadLocal是如何存储线程本地变量的呢?先给个简单的结论。
每个Thread在生命周期中都会维护着一个ThreadLocalMap,可以看成是一个存储了ThreadLocal(key)—value的HashMap,当ThreadLocal存储value时,先通过当前Thread得到其维护的ThreadLocalMap,然后将其存储到该map中,而获取value时则是先获取到当前线程的ThreadLocalMap,然后通过当前的ThreadLocal,获取到ThreadLocalMap存储的value值。

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);
}

 set方法中,首先通过Thread.currentThread()获取到当前的线程,通过当前线程获得其维护的ThreadLocalMap,当map为空时,则为当前Thread创建一个ThreadLocalMap,不为空的话则将ThreadLocal–value存储到map中。
所以一个Thread对应着一个ThreadLocalMap,而一个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) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

 get()方法,同样是先获取到当前Thread,然后获取到当前Thread的ThreadLocalMap,然后根据ThreadLocal自身,通过ThreadLocalMap自身的寻址方式获取到存储ThreadLocal和value的Entry对象,进而得到value。
 setInitialValue();是当ThreadLocalMap为空时,可以通过实现ThreadLocal的initialValue()来获得一个默认值,同时该默认值会被存储到线程的ThreadLocalMap中。


内存泄漏

 分析到这里,ThreadLocal的原理已经很明朗了。但是一些使用不当的情况出现内存泄漏的风险,所以最后讲解下ThreadLocal会出现的内存泄漏风险,及如何避免。
 ThreadLocalMap中存储的Entry为ThreadLocal–value,准确的描述应该是weakReference(ThreadLocal)–value,即,key(ThreadLocal为弱引用),而value则是强引用的,当ThreadLocal为空后,Thread不会再持有ThreadLocal引用,ThreadLocal可以被GC回收,但是Thread的ThreadLocalMap仍然还持有value的强引用,导致value需要等待线程生命周期结束才可能被GC回收。当出现一些长时间存在的线程,不断的存储了内存比较大的value,而value实际是不再被使用的,value由于线程没有被回收而不断的堆积,造成了内存泄漏。比如当使用到线程池是,Thread很有可能不会被马上结束,可能会被不断的重复利用。
 所以这里引入ThreadLocal的另外一个方法——remove方法

remove()方法
    public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }


    //ThreadLocalMap中的remove
    private void remove(ThreadLocal<?> key) {
            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)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

 所以在value不再使用时,应该及时调用remove,解除线程对该value的引用。

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

ThreadLocal,看我就够了! 的相关文章

  • Android Studio:XML 布局中的“包装在容器中”

    编辑 XML 布局文件时 Eclipse 有一项称为 包裹在容器中 的功能 重新格式化 gt Android gt 可让您选择一个或多个视图并在其周围包裹您选择的布局 Android Studio中有类似的东西吗 目前正在实施中 问题 69
  • 如何正确释放Android MediaPlayer

    我正在尝试向我的 Android 应用程序添加一个按钮 当点击该按钮时它会播放 MP3 我已经让它工作了 但没有办法释放 mediaPlayer 对象 因此即使在我离开活动后它仍然会继续播放 如果我在react 方法之外初始化MediaPl
  • 按下按钮时应用不同的样式

    有没有办法在按下按钮时将样式应用于按钮 如果我有一种风格样式 xml
  • Android Q:file.mkdirs() 返回 false

    我们有一个应用程序 使用外部存储来存储一些临时文件 图像 二进制数据 该代码已经运行了几年 直到最近才发生重大变化 在 Android Q 上它不起作用 File f new File Environment getExternalStor
  • 如何在 Linux 内核中定义并触发我自己的新软中断?

    我想在 Linux 内核中创建自己的软中断 这是正确的方法吗 In the init我想触发该模块的softirq我将添加一个调用 394 void open softirq int nr void action struct softir
  • ExoPlayer2 - 如何使 HTTP 301 重定向工作?

    我开始使用 ExoPlayer 来传输一些音频 一切都很顺利 直到我遇到一个带有 301 永久移动 重定向的 URL ExoPlayer2 默认情况下不处理该问题 我已经看过这个线程 https github com google ExoP
  • Android Eclipse 上的 Web 服务

    我是 android eclipse java 的新手 事实上这个论坛也是如此 有人遇到过这种情况吗 从用户那里获取输入并通过使用 android eclipse 中的 Web 服务来显示适当的结果 有可用的示例吗 非常感谢 我正在发布教程
  • 如何在谷歌地图android上显示多个标记

    我想在谷歌地图android上显示带有多个标记的位置 问题是当我运行我的应用程序时 它只显示一个位置 标记 这是我的代码 public class koordinatTask extends AsyncTask
  • 对于一个单元格,RecyclerView onBindViewHolder 调用次数过多

    我正在将 RecyclerView 与 GridLayoutManager 一起使用 对于网格中的每个项目 我需要调用 REST api 来检索数据 然后 从远程异步获取数据后 我使用 UIL 加载 显示图像 一切似乎都很好 但我发现 on
  • 使用 AsyncTask 传递值

    我一直在努力解决这个问题 但我已经到了不知道该怎么办的地步 我想做的是使用一个类下载文件并将其解析为字符串 然后将该字符串发送到另一个类来解析 JSON 内容 所有部件都可以单独工作 并且我已经单独测试了所有部件 我只是不知道如何将值发送到
  • Android 2.3 模拟器在更新位置时崩溃

    我正在使用 Eclipse 编写和调试 Android 应用程序 我需要做的事情之一是更新设备的位置 因此我尝试使用模拟器控制窗口中的位置控制面板 在 手动 选项卡上 我选择 十进制 输入有效的纬度和经度 然后单击 发送 不幸的是 接下来发
  • 如何在 Android 中从 WorkManager 取消工作?

    我已经保存了 WorkManagerUUID转换成String在领域数据库中 这是代码 Constraints constraints new Constraints Builder setRequiredNetworkType Netwo
  • 您使用什么物理 Android 设备进行测试?

    有什么好的推荐用于测试目的的物理 Android 设备吗 我正在苹果阵营寻找像 iPod touch 这样的设备 可以帮助 iOS 开发人员测试他们的东西 我知道有 Nexus One 但那东西相当昂贵 而且我并不真正关心手机的东西 而是可
  • Android:无法使用 DbHelper 和 Contract 类将数据插入 SQLite

    public class Main2Activity extends AppCompatActivity private EditText editText1 editText2 editText3 editText4 private Bu
  • Dagger 2 没有生成我的组件类

    我正在使用 Dagger 2 创建我的依赖注入 几个小时前它还在工作 但现在不再生成组件 这是我创建组件的地方 public class App extends Application CacheComponent mCacheCompon
  • 找不到符号 NOTIFICATION_SERVICE?

    package com test app import android app Notification import android app NotificationManager import android app PendingIn
  • 通过系统应用程序以编程方式静默安装 apk(无需 root)

    我有带有 android sharedUserId android uid system UID 1000 的系统级应用程序 设备未root INSTALL PACKAGES 权限包含在清单中 我可以静默安装下载的 apk 吗 我已经发现这
  • 通过电子邮件发送文本文件附件

    我正在尝试附加一个文本文件以便通过电子邮件发送 但每当我打开电子邮件应用程序时 它都会说该文件不存在 请帮助 Intent i new Intent Intent ACTION SEND i setType text plain i put
  • Android:如何从网络异步获取搜索建议?

    我创建了一个可搜索的活动 现在 我想添加从网络服务获取的搜索建议 我想异步获取这些建议 根据添加自定义建议 http developer android com guide topics search adding custom sugge
  • Git 实验分支还是单独的实验存储库?

    我正在开发一个 Android 应用程序 并且在整个开发周期中一直使用 Git 现在 我想构建并发布实验性功能 供人们尝试和安装 同时仍将原始的 稳定的应用程序安装在他们的设备上 现在 这意味着我需要使用不同的包名称 这会更改开发项目中的一

随机推荐

  • 浏览器页面不能正常显示

    1 考虑是不是浏览器的vpn和软件vpn冲突导致的浏览器页面不能正常显示 2 考虑是不是自己的hosts文件 将一些网址指向了 127 0 0 1 因为我修改过hosts文件 指向了一些网址到这个IP 而我的浏览器VPN也手动代理了这个IP
  • Netty (2)-ChannelInboundHandlerAdapter入站事件

    在第1篇 我们继承ChannelInboundHandlerAdapter后 即可收到消息并处理 本篇介绍其更多的用法 基本概念 Channel 可以理解为一个连接 每一个客户端连到服务器 都会有一个与之对应的Channel Channel
  • 嫁给程序员的十大好处

    医生 医生很危险 我对医生的印象太差 放下他身边女生太多 会跟某个护士小姐跑掉 或是被一个假装生病的年轻女人勾引走不说 而且这种倒霉事还往往发生在你已经为他生了几个小孩的时候 现在的医生都是领提成的昧着良心看病 有钱就是病人 没有钱死了都没
  • 【学习笔记】李宏毅2020ML&DL课程 13_2 Unsupervised Learning

    Neighbor embedding manifold learning LLE 地球就是一个流形 流形学习就是将高维的 流形的 feature的摊平 摊平之后就可以用欧氏距离了 假设空间中有两个点xi和xj 他们俩的关系为wij LLE方
  • Java学习之:如何将 java 程序打包成 .jar 文件

    文章目录 开始打包 打开文件结构 选中 Artifacts 点 from modules with dependencies 选择想打包的 module 选择 Server Module 中的 main 函数所在的文件 确认即可 对 Cli
  • Node基础(特点,安装运行及命令行及CMD相关命令)

    目录 node简介 node的定义 node的特点 node与js的区别 node的应用领域 node的安装 编写第一个node代码 命令行与CMD CMD的概念 打开CMD的方法 cmd的相关命令 node的全局变量 Buffer 缓冲区
  • 进制转换算法实现

    进制转换算法实现 在计算机科学和数学中 进制转换是将一个数从一种进制表示转换为另一种进制表示的过程 常见的进制包括二进制 八进制 十进制和十六进制 在本文中 我们将使用Python编程语言来实现进制转换算法 二进制转换为其他进制 我们首先来
  • 双硬盘SSD+HDD,安装win10+Ubuntu18.04双系统(安装超详解)(UEFI启动+GPT分区)(或许是你正在解决的问题?)

    本文在WIN10下Ubuntu 18 04 磁盘格式是GTP 同时使用UEFI引导 Win10安装在固态盘 Ubuntu引导启动项在固态盘 系统在机械盘 本文适用于 UEFI模式 GPT 注意 网上有些教程已经过时 不自在适应新机器 或许你
  • 烂泥虚拟机硬盘简简单单扩容

    今天写这个博客就是为了 以后各位午饭们在使用虚拟机为虚拟机硬盘扩容时 不要再走那么多弯路了 环境介绍 虚拟机 VMware Workation 8 操作系统 windows server 2008 硬盘容量 10G windows serv
  • 微信H5页面内实现一键关注公众号

    H5页面内实现关注公众号的微信JSSDK没有相关接口开放 因此就得动点脑筋来实现该功能了 下面的方法就是通过一种非常蹊跷的方式实现的 首先 需要在公众号内发表一篇原创文章 注意是原创文章 然后由另一个公众号去转载该文章 注意是转载 不是转发
  • uniapp scroll-view 隐藏滚动条

    清除滚动条 适配安卓 webkit scrollbar width 0 height 0 color transparent 清除滚动条 适配IOS webkit scrollbar display none
  • 环境感知算法——4.RandLA-Net基于SemanticKITTI训练

    1 前言 RandLA Net Random Sampling and Local Feature Aggregator Network 是一种处理点云数据的神经网络结构 采用随机采样 Random Sampling RS 以降低点云密度并
  • 【华为OD机试】组成最大数【2023 B卷

    华为OD机试 真题 点这里 华为OD机试 真题考点分类 点这里 题目描述 小组中每位都有一张卡片 卡片上是6位内的正整数 将卡片连起来可以组成多种数字 计算组成的最大数字 输入描述 号分割的多个正整数字符串 不需要考虑非数字异常情况 小组最
  • 多变的智能降噪

    告警 作为监控的平台的最直观的体现形式 可以体现出被 监控者 的当前状态 你可以看到它是健康十足的平稳状态 亦或是偶尔发出告警的异常状态 甚至是告警癫狂的崩溃状态 这都是最直观的告诉你他是否需要你的方式 但如果没有好的梳理方式 反而会让人没
  • android studio 突然无法启动 if you already have a 64-bit jdk installed,define a java_home variable in

    控制台输出乱码 按照方法处理 然后就无法启动 鼠标选中Android studio应用 双击shift键 弹出框 输入vmoption 添加 Dfile encoding UTF 8 Android studio控制台 输出乱码解决方法 n
  • 网络请求及协议

    TCP IP 协议 图解HTTP常见问题 归类 目录
  • 《clickhouse原理解析与应用实践》读书笔记

    福利置顶 温馨提示 电子版可在微信读书app阅读 第一章 ClickHouse的前世今生 传统BI的局限性 数据仓库 为了解决数据孤岛的问题 即通过引入一个专门用于分析类场景的数据库 将分散的数据统一汇聚到一处 数据仓库的衍生概念 对数据进
  • Docker网络学习

    文章目录 Docker容器网络 1 Docker为什么需要网络管理 2 Docker网络简介 3 常见的网络类型 4 docker 网络管理命令 5 两种网络加入差异 6 网络讲解 docker Bridge 网络 docker Host
  • 腾讯云私有云平台运维面试

    文章目录 概述 JD 岗位描述 一面 二面 三面 HR面 概述 根据会议将面试问题进行总结 很多问题感觉当时没回答好 这是为啥呢 应该还是不熟练吧 或者不善于表达 将次经历分享出来 大家多练练 JD 岗位描述 私有云平台运维 JD 腾讯云智
  • ThreadLocal,看我就够了!

    ThreadLocal 开胃菜 研究过Handler的应该对ThreadLocal比较眼熟的 线程中的Handler对象就是通过ThreadLocal来存放的 初识ThreadLocal的可能被它的名字有所误导 ThreadLocal初一看