ThreadLocal 的简单使用并深扒其实现原理

news2024/11/28 2:37:43

在多线程环境下, 如果想要保证每个线程都能独立于其它线程独自运行, 可以使用 ThreadLocal 来解决; ThreadLocal 就是用于提供线程局部变量的一个工具, 也就是说 ThreadLocal 可以为每个线程创建一个单独的变量副本; 其概念与同步机制正好相反, 同步机制是保证多线程环境下数据的一致性; 而 ThreadLocal 则是保证多线程环境下数据的独立性.
本文将以代码的形式展示 ThreadLocal 的简单使用方式以及一些内部方法的原理.

ThreadLocal

  • 1 ThreadLocal 简单使用
  • 2 ThreadLocal 的实现
    • 2.1 set 方法
    • 2.2 get 方法
    • 2.3 remove 方法
    • 2.4 总结
  • 3 (了解) 底层原理实现
    • 3.1 构造方法
    • 3.2 存储结构
    • 3.3 存储对象 Entry
    • 3.4 保存键值对
    • 3.5 获取 Entry 对象
    • 3.6 移除指定的 Entry
  • 4 关于内存泄露

1 ThreadLocal 简单使用


public static void main(String[] args) {

        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                threadLocal.set("我是线程 1");
                System.out.println(threadLocal.get());

                try {
                    // 测试如果移除了线程 2 后, 线程 1 是否还能够打印
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(threadLocal.get());
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                threadLocal.set("我是线程 2");
                System.out.println(threadLocal.get());
                threadLocal.remove();
                System.out.println("线程 2 移除了");
            }
        });
        thread1.start();
        thread2.start();
    }
}

运行结果:
在这里插入图片描述
代码解读:
此代码就展示了两个线程环境下独立运行情况, 加入一个 5 s 时间的延迟是为了查看移除线程 2 后, 线程 1 是否还能够正常打印数据; 根据运行结果可以看得出两个线程互不影响.


那么 ThreadLocal 是怎样保证两个线程中的数据都是独立的呢 ¿ ?

2 ThreadLocal 的实现

  要想知道 ThreadLocal 的实现原理, 首先要知道两个方法是如何实现的: set 方法, get 方法及 remove 方法.

2.1 set 方法

 进入到 set 方法里面, 可以看到如下代码:
在这里插入图片描述

  • set 方法中的步骤是先获取到当前的线程 (Thread.currentThread()), 然后再去获取到当前线程的 ThreadLocalMap;
  • 判断: 如果 ThreadLocalMap 不为空, 就将值保存到 ThreadLocalMap 中, 并用当前的 ThreadLocal 作为 key (map.set(this, value));
  • 如果 ThreadLocalMap 为空, 则创建一个 ThreadLocalMap 并给到当前线程, 保存 value 值;
  • ThreadLocalMap 就相当于是个 HashMap, 这才是真正保存值的地方.

2.2 get 方法

  进入到 get 方法, 代码如下:
在这里插入图片描述

  • 第一步还是获取到当前的线程 (Thread.currentThread()), 然后获取到当前线程的 ThreadLocalMap;
  • 判断: 如果 ThreadLocalMap 不等于空, 就取出当前 ThreadLocal 的值;
  • 如果 ThreadLocalMap 为空, 则调用 setInitialValue() 方法返回初始值, 并保存到新创建的 ThreadLocalMap 中 (与 set 方法基本一致).

2.3 remove 方法

在这里插入图片描述
remove 方法比较简单, 也是先获取到当前线程的 ThreadLocalMap, 然后删除就可以了.

2.4 总结

  • 在上面的三种方法第一步都会获取到当前的线程, 然后通过当前的线程去获取到 ThreadLocalMap, 如果 ThreadLocalMap 为空, 就会创建一个 ThreadLocalMap 并给到当前的线程. 可以看出, 每一个线程都会持有一个 ThreadLocalMap 用来维护线程本地的值.
  • 在使用 ThreadLocal 类型变量进行相关操作, 都会通过当前线程获取到 ThreadLocalMap 来完成操作; 每个线程的 ThreadLocalMap 是属于线程自己的, ThreadLocalMap 中维护的值也是属于线程自己的, 这就保证了 ThreadLocal 类型的变量在每个线程中都是独立存在的, 在多线程环境下也互不影响.

3 (了解) 底层原理实现


3.1 构造方法

  ThreadLocal 中当前线程的 ThreadLocalMap 为空时会使用 ThreadLocalMap 的构造方法去新建一个 ThreadLocalMap, 如下:
在这里插入图片描述

通过源码可以看到, 构造的时候会新建一个 Entry 类型的数组, 并将第一次需要保存的键值存储到一个数组中, 完成一些初始化操作.

3.2 存储结构

ThreadLocalMap 内部维护了一个哈希表来存储数据, 并且定义了加载因子等, 如下所示:
在这里插入图片描述

3.3 存储对象 Entry

Entry 用于保存一个键值对, 如下:
在这里插入图片描述

3.4 保存键值对

  当调用 set 方法将数据保存到哈希表中;
在这里插入图片描述

  • 首先使用 key 的 threadLocalHashCode 来计算要存储的索引位置, threadLocalHashCode 的值由 ThreadLocal 类管理, 每创建一个 ThreadLocal 对象都会自动生成一个相应的 threadLocalHashCode 值;
  • 在保存数据的时候, 如果若索引位置由 Entry, 且里面的 key 为空, 就会执行清除无效的 Entry 操作, 因为 Entry 的 key 使用的是弱引用的方式, key 如果被回收, 这是就无法再访问到 key 对应的 value, 因此需要把无效的 Entry 清除掉腾出空间;
  • 当然在调整 table 容量的时候也会先清除无效的 Entry 对象, 然后再根据需要进行扩容操作.

3.5 获取 Entry 对象

取值操作是直接获取到 Entry 对象, 使用 getEntry 方法, 如下:
在这里插入图片描述

  • 先是使用指定的 key 的 HashCode 计算索引位置;
  • 获取到当前位置的 Entry, 如果 Entry 不为 null 且 key 和执行的 key 相等, 则返回该 Entry; 否则就调用 getEnterAfterMiss 方法 (因为可能存在哈希冲突, key 对应的 Entry 的存储位置可能不在 key 计算出的索引位置上, 也就是说索引位置上的 Entry 不一定是 key 对应的 Entry, 所以需要调用 getEnterAfterMiss 方法获取).

3.6 移除指定的 Entry

在这里插入图片描述

4 关于内存泄露

在 ThreadLocal 的 get / set / remove 方法中, 都有清楚无效的 Entry 的操作, 这样做的目的就是为了降低内存泄露发生的可能.

导致内存泄露的原因:
假设 Entry 中的 key 没有使用弱引用 (弱引用就是无论空间是否充足, 都可以进行回收, 当然强引用使我们普遍使用的引用)的方式, 由于 ThreadLocalMap 的生命周期和当前线程一样长, 那么当引用 ThreadLocal 的对象被回收后, 由于 ThreadLocalMap 还持有 ThreadLocal 和对应的 value 的强引用, ThreadLocal 和对应的 value 是不会被回收的, 这就导致了内存泄露;
所以 Entry 以弱引用的方式避免了 ThreadLocal 没有被回收而导致的内存泄露, 但是此时的 value 仍然是无法回收的, 依然会导致内存泄露.

但是, ThreadLocalMap 已经考虑到了这种情况的存在, 因此在调用 get / set / remove 方法时会清除掉当前线程 ThreadLocalMap 中所有的 key 为 null 的 value; 这样就降低了内存泄露发生的概率; 所以我们在使用 ThreadLocal 的时候, 每次用完 ThreadLocal 都会调用 remove() 方法, 清除数据, 防止内存泄露.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/402438.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

elasticSearch写入原理

elasticSearch写入原理 最近学习完了es相关的课程整理除了es的核心内容,学习这东西知其然知其所以然,自己按照自己的理解整理了es相关的面试题。先热个身,整理一下es的写入原理,有不对的地方请大家指正。 这些原理的东西我觉得还是…

MySql数据库(进阶篇)

👌 棒棒有言:人生总是在前行,不论走到哪里,只要带着信念往前走,比别人多一点努力,你就会多一份成绩;比别人多一点志气,你就会多一份出息;比别人多一点坚持,你…

js学习3(数组)

目录 结构图 数组操作 每日一练 结构图 数组操作 ## 数组中可以存储任何类型元素 ## 创建: 字面量([...])、创建对象(new Array(arr_len)) ## 遍历: 循环遍历、forEach(callback)、map(callback)、filter(callback)、every(callback)、some(callback)、…

1637_fgets函数的功能

全部学习汇总: GreyZhang/g_unix: some basic learning about unix operating system. (github.com) 没想到分析一个函数的实现会这么麻烦,中间利用的一系列的库函数以及内核接口我全都不熟悉。但是,这次的这个函数应该是之前C语言的基本教程…

vscode下载与使用

1.vscode下载 官网下载地址:Download Visual Studio Code - Mac, Linux, Windows下载太慢,推荐文章:解决VsCode下载慢问题_vscode下载太慢_迷小圈的博客-CSDN博客下载太慢,推荐下载链接:https://vscode.cdn.azure.cn/s…

JavaScript Array(数组)对象

数组对象的作用是:使用单独的变量名来存储一系列的值。参数参数 size 是期望的数组元素个数。返回的数组,length 字段将被设为 size 的值。参数 element ...; elementn 是参数列表。当使用这些参数来调用构造函数 Array() 时,新创建的数组的元…

SEO技术风口来了|SEO能否抓住全球约93%的网络用户?

开篇词作者/出品人 | 美洽 SEO 流量专家 白桦为什么要做一个 SEO 专栏?在一部分人眼中,SEO(搜索引擎优化)已经是老掉牙的玩意儿,在这个信息爆炸的年代,它似乎已经无法承担吸引流量的主要作用。但&#xff…

2023年3月北京/广州/杭州/深圳数据治理工程师认证DAMA-CDGA/CDGP

DAMA认证为数据管理专业人士提供职业目标晋升规划,彰显了职业发展里程碑及发展阶梯定义,帮助数据管理从业人士获得企业数字化转型战略下的必备职业能力,促进开展工作实践应用及实际问题解决,形成企业所需的新数字经济下的核心职业…

代码随想录算法训练营第二十四天 | 理论基础、 77. 组合

打卡24天,今天学第七章回溯算法,之前已经学过一遍,现在学第二遍,加油。 今日任务 理论基础77.组合 理论基础 什么是回溯 递归的副产物,有递归就会有回溯 红色的箭头就是回溯 回溯的效率 回溯的本质是穷举&#xff…

ROS通信机制

参考: bilibiliAutolabor官方 回调函数:! 由外部中断激发而执行的函数,函数执行的时间不由函数本身控制,而是由外部激发 1、话题通信 ​ publisher发布者,和subscriber订阅者通过topic相互连接&#xf…

设计模式5——自定义Spring框架

1、Spring核心功能结构 Spring大约有20个模块,由1300多个不同的文件构成。这些模块可以分为:核心容器、AOP和设备支持、数据访问与集成、Web组件、通信报文和集成测试等。下面是Spring框架的整体架构图: 核心容器由beans、core、context 和 …

Vue脚手架的安装(保姆级教程)

Vue脚手架的安装(保姆级教程) 文章目录Vue脚手架的安装(保姆级教程)1.下载vscode2.node下载5.Vue脚手架的安装6.创建Vue项目7.项目的运行1.下载vscode vscode下载地址 2.node下载 node下载 1.打开cmd node -vnpm -v2.在node的…

linux入门---vim的配置

这里写目录标题预备知识如何配置vimvim一键配置预备知识 在配置vim之前大家首先得知道一件事就是vim的配置是一人一份的,每个用户配置的vim都是自己的vim,不会影响到其他人,比如说用户xbb配置的vim是不会影响到用户wj的,虽然不同…

开源写作平台WriteFreely(折腾篇)

设置向导 除了直接修改 config.ini 之外,你还可以进入容器用命令行进行设置 在 SSH 客户端执行下面的命令 # 生成 config.ini docker exec -it writefreely ./writefreely --config也可以通过群晖的 终端机 一步一步跟着填入就可以了,但是需要说明的是…

Zhong__Celery基本使用详解

时间:2023.03.10环境:python3/centos/redis目的:演示celery基本使用的详细案例说明:python依赖的版本以requirement.txt文件为测试基准 不同版本可能存在差异作者:Zhong简介简介及概念介绍部分不会很详细 主要看demo项…

【HTTP协议与Web服务器】

HTTP协议与Web服务器浏览器与服务器通信过程HTTP的请求报头HTTP请求报头结构HTTP的请求方法HTTP应答报头HTTP应答报头结构应答状态web服务器的c语言实现浏览器与服务器通信过程 浏览器与Web服务器再应用层通信使用的是HTTP协议,而HTTP协议在传输层使用的是TCP协议。…

深度学习必备知识——模型数据集Yolo与Voc格式文件相互转化

在深度学习中,第一步要做的往往就是处理数据集,尤其是学习百度飞桨PaddlePaddle的小伙伴,数据集经常要用Voc格式的,比如性能突出的ppyolo等模型。所以学会数据集转化的本领是十分必要的。这篇博客就带你一起进行Yolo与Voc格式的相互转化&…

数据库系统概论

文章目录前言基础篇:1-5章第 1 章 绪论1.1 数据库系统概述1.2 数据模型1.3 数据库系统的结构1.4 数据库系统的组成1.5 小结第 2 章 关系数据库1.关系模型1.1 关系数据结构1.2 关系完整性约束实体完整性、参照完整性、用户定义完整性2.关系代数8种关系代数运算符并 ∪…

「媒体邀约」如何选择适合的媒体公关,媒体服务供应商

传媒如春雨,润物细无声,大家好,我是51媒体网胡老师。 每天胡老师也会接到大量关于媒体方面的询问,胡老师也都一一的很耐心的进行了解答,也都很详细的做了媒体规划和媒体传播方案,但有的朋友还是很犹豫&…

关于 @Aspect 注解的使用

一、Spring AOPAOP(Aspect Oriented Programming) 是一种面向切面的编程思想。面向切面编程是将程序抽象成各个切面,即解剖对象的内部,将那些影响了多个类的公共行为抽取到一个可重用模块里,减少系统的重复代码,降低模块间的耦合度…