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,看我就够了! 的相关文章

  • Gradle 构建错误:内存不足

    当我使用 gradle 构建时 它失败并显示以下信息 OpenJDK 64 Bit Server VM warning INFO os commit memory 0x0000000788800000 89128960 0 failed e
  • 如何在 ADB 连接期间禁用电池充电?

    问题描述 每次我在电脑和手机之间连接 USB 线时 电池都会自动充电 我想使用 ADB 协议 但我不想在 ADB 连接期间为电池充电 是否可以关闭此充电功能 当然 我该怎么做呢 环境 Android 操作系统 4 及更高版本的手机 我只需要
  • Android 应用程序在启动时打开应用程序信息屏幕,而不是启动主 Activity

    我不确定这是否是一个问题 但这是我第一次遇到这个问题 我正在开发一个应用程序 当我在进行一些编码后断开应用程序与 Android Studio 和 PC 的连接时 如果我尝试在手机上打开应用程序 它会启动app info屏幕 我们看到强制停
  • 如何在android中压缩和解压png图像

    您好 在我的应用程序中 当我单击 zip 按钮时 我需要压缩图像文件 当我单击解压缩按钮时 我需要解压缩文件 我尝试使用下面的代码来压缩图像 但我的问题是当我单击 zip 按钮时 正在创建 zip 文件 但之后在使用 winzip 软件的系
  • API29 上不推荐使用 setColorFilter

    我使用以下行来更改 VectorDrawable 的颜色 mydrawable getBackground setColorFilter color PorterDuff Mode SRC ATOP 这很好用 尽管它现在已被弃用 文档建议我
  • 如何在 Android TextView 中使用土耳其语字符,如“ş ç ı ö”?

    我想在 android TextView 中写入 ile 但它没有正确绘制 怎样才能使用这样的字符呢 例如 我将文本视图设置为 ile 它显示为 ile 我怎样才能解决这个问题 尝试以下方法 看看是否有帮助 source http grou
  • 更改卡片高度即更改 Jetpack 中与 Material 3 组合的卡片颜色

    我正在使用 Card 可组合项 我希望它的颜色为白色 但是当我向它添加一些高度时 它的颜色会更改为更像主要容器颜色 我看过文档 其中有一种称为高程覆盖的东西 但找不到说明如何使用它的示例 这是我的代码 Card modifier Modif
  • 位图内存不足错误

    我对这个错误有疑问 我从 URL 制作网站图标解析器 我这样做是这样的 public class GrabIconsFromWebPage public static String replaceUrl String url StringB
  • 具有自定义源集的 Android Gradle 风格 - gradle 文件应该是什么样子?

    我有一个旧的 eclipse 项目 我已经转移到 android studio 并设置为使用flavor 它似乎工作得很好 直到我开始尝试在我的风格之间使用不同的 java 文件 我的项目设置是这样的 ProjectRoot acitonb
  • Android Fragment onCreateView 与手势

    我正在尝试在片段中使用手势 我在 FragmentActivity 中有以下内容来处理我的详细信息片段 我试图发生的情况是 当在视图上检测到滑动时 将该视图内的数据替换为上一个或下一个条目 如果有更好的方法来处理这个问题 我完全同意 然而
  • 来自相机的 MediaCodec 视频流方向和颜色错误

    我正在尝试流式传输视频捕获直接从相机适用于 Android 设备 到目前为止 我已经能够从 Android 相机捕获每一帧预览帧 byte data Camera camera 函数 对数据进行编码 然后成功解码数据并显示到表面 我用的是安
  • 如何从android中的外部存储中获取所选文件的文件路径?

    我在选择文件的文件路径时遇到问题 我搜索了整个堆栈溢出 但问题没有解决 从设备中选择文件的代码如下所示 Intent intent new Intent Intent ACTION GET CONTENT intent setType in
  • 适用于 Android 的 Google 云端硬盘\文档 API

    我在几个小时内将 Dropbox 与我的应用程序集成 因为 SDK 描述清晰并且有很好的使用示例 Google Drive 似乎只有一个 一刀切 的 Gdata SDK 它非常重 有很多依赖项 它使我的应用程序的大小增加了三倍 而且不是很直
  • 有多少种方法可以将位图转换为字符串,反之亦然?

    在我的应用程序中 我想以字符串的形式将位图图像发送到服务器 我想知道有多少种方法可以将位图转换为字符串 现在我使用 Base64 格式进行编码和解码 它需要更多的内存 是否有其他可能性以不同的方式做同样的事情 从而消耗更少的内存 现在我正在
  • Jetpack 导航:如何从一个嵌套图的子级导航到另一个嵌套图的子级?

    导航结构 MainActivity nav root HomeFragment AuthNestedGraph nav auth BeforeOtpFragment home OtpFragment ProfileNestedGraph n
  • Android 中的列表视图分页

    我有一个列表视图 其中显示了 50 个元素 我决定对视图进行分页 以便视图的每个部分都有 10 个元素 然后单击 下一个 按钮以获取下一个 10 个元素 如何设置10个数据 我关注这篇文章http rakhi577 wordpress co
  • SDK >=26 仍需要 mipmap/ic_launcher.png?

    在 Android 中 有两种指定启动器图标 可以说是应用程序图标 的方法 老 方式 在 mipmap 文件夹中指定不同的 png 文件 通常命名为 ic launcher png 但可以通过以下方式设置名称android icon mip
  • Android Gradle 同步失败:无法解析配置“:classpath”的所有工件

    错误如下 Caused by org gradle api internal artifacts ivyservice DefaultLenientConfiguration ArtifactResolveException Could n
  • 在android中创建SQLite数据库

    我想在我的应用程序中创建一个 SQLite 数据库 其中包含三个表 我将向表中添加数据并稍后使用它们 但我喜欢保留数据库 就好像第一次安装应用程序时它会检查数据库是否存在 如果存在则更新它 否则如果不存在则创建一个新数据库 此外 我正在制作
  • 如何在布局编辑器中模拟沉浸式模式

    我想在布局编辑器中全屏查看我的布局 我正在使用 eclipse 插件 我已经通过选择隐藏了 ActionBar NoActionBar组合中的主题 但导航栏是一个不同的故事 AFAIK 它只能使用代码中的标志来隐藏 我需要在活动 xml 文

随机推荐

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

    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初一看