『进阶之路』- 揭开ThreadLocal神秘面纱

news2024/11/19 23:41:27

阅读本文主要可以解决以下困惑:

  1. 什么是ThreadLocal,隔离线程的本地变量
  2. ThreadLocal的数据结构是怎么样的,为什么能实现线程隔离
  3. ThreadLocal的get和set方法
  4. ThreadLocal如何实现的线程安全?结合同步锁机制,空间换取时间
  5. ThreadLocal为什么会出现内存泄漏
  6. ThreadLocal在Handler中的应用?如何保证Thread和Looper的一一对应关系的

ThreadLocal是什么

ThreadLocal 叫做本地线程变量,意思是说,ThreadLocal 中填充的的是当前线程的变量,该变量对其他线程而言是封闭且隔离的,ThreadLocal 为变量在每个线程中创建了一个副本,这样每个线程都可以访问自己内部的副本变量。
根据这一特点ThreadLocal可以隔离线程

ThreadLocal的数据结构

如何理解ThreadLocal的结构
ThreadLocal的数据结构类似于 HashMap<Thread , ThreadLocalMap<ThreadLocal , T>>
首先我们根据线程Thread作为Key值获取到,每一个线程中的 ThreadLocalMap,而ThreadLocalMap又是一个Map结构,其中ThreadLocal实例作为Key值,存储在该ThreadLocal中的值作为Value
ThreadLocalMap在ThreadLocal中get()方法和set()方法都有可能创建该线程的ThreadLocalMap(最终他们都调用了createMap()方法

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

ThreadLocalMap,这个类没有实现map的接口,就是一个普通的java类,但是实现的类就类似于map的功能,数据用Entry存储,Entry继承于WeakReference,用一个键值对来存储**,键就是ThreadLocal的引用**。每一个线程都有一个ThreadLocalMap的对象,每一个新的线程Thread都会实例化一个ThreadLocalMap并赋予值给成员变量Thread.threadLocals。

public class ThreadLocalTest {
    public static void main(String[] args) {
        MyRunnable runnable=new MyRunnable();
        new Thread(runnable,"线程1").start();
        new Thread(runnable,"线程2").start();
    }
    public static class MyRunnable implements Runnable{
        ThreadLocal<String> threadLocal1= ThreadLocal.withInitial(() -> "null");
        ThreadLocal threadLocal2= ThreadLocal.withInitial(() -> "null");
        @Override
        public void run() {
            String name = Thread.currentThread().getName();
            threadLocal1.set(name+"的threadLocal1");
            threadLocal2.set(name+"的threadLocal2");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(name + ": "+ threadLocal1.get());
            System.out.println(name + ": "+ threadLocal2.get());
        }
    }
}

线程2: 线程2的threadLocal1
线程1: 线程1的threadLocal1
线程2: 线程2的threadLocal2
线程1: 线程1的threadLocal2

源码分析

ThreadLocal#get()

public T get() {
    //1.获取到当前的线程
    Thread t = Thread.currentThread();
    //2.通过当前线程获取到ThreadLocalMap对象
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        //3.通过当前的ThreadLocal对象
        //获取到Entry对象(其中封装着value)
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

ThreadLocal#set()

public void set(T value) {
    //1.获取到当前线程
    Thread t = Thread.currentThread();
    //2.根据线程获取到ThreadLocalMap
	//由此可知Thread作为Key,而ThreadLocalMap作为Value
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

ThreadLocal如何做到线程安全

每个线程拥有自己独立的 ThreadLocals变量(指向ThreadLocalMap对象 )
每当线程 访问 ThreadLocals变量时,访问的都是各自线程自己的ThreadLocalMap变量(键 - 值)
ThreadLocalMap变量的键 key = 唯一 = 当前ThreadLocal实例

ThreadLocal与同步机制的区别

image.png

为什么ThreadLocal的键是弱引用

image.png
如果使用强引用,当ThreadLocal 对象的引用(强引用)被回收了,ThreadLocalMap本身依然还持有ThreadLocal的强引用,如果没有手动删除这个key ,则ThreadLocal不会被回收,所以只要当前线程不消亡,ThreadLocalMap引用的那些对象就不会被回收, 可以认为这导致Entry内存泄漏。

ThreadLocal的内存泄漏

重点来了,如果一种情况下我们ThreadLocal是null了,也就是要被垃圾回收器回收了,但是此时我们的ThreadLocalMap(因为它是Thread类 的内部属性)生命周期和Thread的一样,它不会回收,这时候就出现了一个现象。那就是ThreadLocalMap的key没了,但是value还在,这就造成了内存泄漏。
解决办法:使用完ThreadLocal后,执行remove操作,避免出现内存溢出情况。
所以 如同 lock 的操作 最后要执行解锁操作一样,ThreadLocal使用完毕一定记得执行remove 方法,清除当前线程的数值。
如果不remove 当前线程对应的VALUE ,就会一直存在这个值。
使用了线程池,可以达到“线程复用”的效果。但是归还线程之前记得清除ThreadLocalMap,要不然再取出该线程的时候,ThreadLocal变量还会存在。这就不仅仅是内存泄露的问题了,整个业务逻辑都可能会出错。

ThreadLoacal的使用实例

首先讲解以下Handler中四兄弟的对应关系
Looper和MeassgeQueue一一对应,因为消息队列是Looper创建的。这个很好理解
Thread和Looper也是一一对应,那这种对应关系是如何建立的呢?
例如我们在一个子线程中要使用Handler,我们首先会去创建一个Looper,使用Looper.prepare()初始化当前线程的Looper,而在Looper的内部又会创建消息队列。

new Thread(new Runnable() {
    @Override
    public void run() {
        Looper.prepare();			//初始化Looper
        new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
            }
        };
        Looper.loop();//开启消息循环
    }
})

可以看到源码中prepare方法的实现就是,将Looper保存在了一个ThreadLocal中,因此每一个线程在创建Looper的时候,这种Thread和ThreadLocal的一一对应关系就建立了。而Looper又存放在当前Thread对应的ThreadLooper中,所以Thread与Looper的一一对应关系是借助着ThreadLocal建立的

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
  ···
private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

再看一下Looper和MessageQueue的一一对应关系的建立,深入以上代码的new Looper(quitAllowed);可以看到就是在私有的构造函数中创建了一个MessageQueue对象。

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

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

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

相关文章

2023年腾讯云双11服务器活动及价格表

双十一购物狂欢节即将到来&#xff0c;腾讯云作为国内领先的云计算服务提供商&#xff0c;推出了一系列优惠活动&#xff0c;下面给大家详细介绍腾讯云双11服务器活动及价格表。 一、腾讯云双11活动入口 活动入口&#xff1a;txy.ink/1111/ 二、腾讯云双11活动时间 即日起至…

智慧巡查平台(Ionic/Vite/Vue3 移动端) 问题记录

目录 1.环境搭建 1.1 安装 node 16 版本 1.2 安装 ionic7 1.3 创建 vue 项目 2.index.html 3.main.ts 3.1 如何默认使用 ios 样式&#xff1f; 3.2 如何使用 ElmentPlus 国际化&#xff1f; 4.router/xxx 5.打包二三事 5.1 添加打包相关文件 5.1.1 .env.developmen…

3、电路综合原理与实践---单双端口理想微带线(伪)手算S参数与时域波形

电路综合原理与实践—单双端口理想微带线&#xff08;伪&#xff09;手算S参数与时域波形与时域波形 1、单理想微带线&#xff08;UE&#xff09;的S参数理论推导 参考&#xff1a;Design of Ultra Wideband Power Transfer Networks的第四章&#xff0c;之后总结推导过程 自…

Kubernetes中如何使用CNI?

一、CNI 是什么 它的全称是 Container Network Interface&#xff0c;即容器网络的 API 接口。 它是 K8S 中标准的一个调用网络实现的接口。Kubelet 通过这个标准的 API 来调用不同的网络插件以实现不同的网络配置方式。实现了这个接口的就是 CNI 插件&#xff0c;它实现了一…

长连接的原理

Apollo的长连接实现是 Spring的DeferredResult来实现的,先看怎么用 import ...RestController RequestMapping("deferredResult") public class DeferredResultController {private Map<String, Consumer<DeferredResultResponse>> taskMap new HashMa…

如何恢复u盘删除文件?2023最新分享四种方法恢复文件

U盘上删除的文件怎么恢复&#xff1f;使用U盘存储文件是非常方便的&#xff0c;例如&#xff1a;在办公的时候&#xff0c;会使用U盘来存储网络上查找到的资料、产品说明等。在学习的时候&#xff0c;会使用U盘来存储教育机构分享的教学视频、重点知识等。而随着U盘存储文件的概…

[数据结构】二叉树

1.概念 一棵二叉树是结点的一个有限集合&#xff0c;该集合&#xff1a; 1. 或者为空 2. 或者是由一个根节点加上两棵别称为左子树和右子树的二叉树组成 从上图我们可以发现&#xff1a; 1.二叉树不存在大于2 的度 2.二叉树的子树有左右之分&#xff0c;次序不能颠倒。是有…

关于内存泄漏的经典面试题

目录 前言 一、内存泄漏基本概念 二、如何判断并查找内存泄漏 1、方案设计 2、方案实现 前言 对于C/C程序员来说&#xff0c;或多或少都会被面试官问到关于内存泄漏的问题&#xff0c;内存泄漏是程序的bug&#xff0c;他会一点一点的侵蚀你的内存&#xff0c;导致程序运行…

jmeter报Java.NET.BindException: Address already in use: connect

1、windows10和window11上&#xff1a; 修改注册表的内容&#xff1a; HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters&#xff1a; 新建dword&#xff08;值&#xff09;的类型&#xff1a; MaxUserPort 65334 TcpTimedWaitDelay 30window

2023年Q3线上生鲜水产数据分析:市场不景气,销额同比下滑44%

事实上&#xff0c;今年线上的生鲜生意市场并不景气。无论是Q1季度还是Q2季度&#xff0c;线上&#xff08;京东平台&#xff09;的销售额均同比去年下滑了10%左右。 然而到了Q3季度&#xff0c;整个下滑态势愈发严峻。可以看到鲸参谋数据显示&#xff0c;7月至9月生鲜水产在京…

腾讯云学生专享云服务器介绍及购买攻略

随着互联网技术的不断发展&#xff0c;越来越多的人开始关注云计算领域。作为国内领先的云计算服务商&#xff0c;腾讯云推出了“云校园”扶持计划&#xff0c;完成学生认证即可购买学生专享云服务器。 一、活动对象 仅限腾讯云官网通过个人认证的35岁以下学生用户参与&#x…

数据结构笔记——树和图(王道408)(持续更新)

文章目录 传送门前言树&#xff08;重点&#xff09;树的数据结构定义性质 二叉树的数据结构定义性质储存结构 二叉树算法先中后序遍历层次展开法递归模拟法 层次遍历遍历序列逆向构造二叉树 线索二叉树&#xff08;难点&#xff09;定义线索化的本质 二叉树线索化线索二叉树中…

Java 使用 poi 和 aspose 实现 word 模板数据写入并转换 pdf 增加水印

本项目所有源码和依赖资源都在文章顶部链接&#xff0c;有需要可以下载使用 1. 需求描述 从指定位置读取一个 word 模板获取业务数据并写入该 word 模板&#xff0c;生成新的 word 文档将新生成的 word 文档转换为 pdf 格式对 pdf 文档添加水印 2. 效果预览 word 模板 带水印的…

【递归、搜索与回溯算法】第六节.98. 验证二叉搜索树和230. 二叉搜索树中第K小的元素

作者简介&#xff1a;大家好&#xff0c;我是未央&#xff1b; 博客首页&#xff1a;未央.303 系列专栏&#xff1a;递归、搜索与回溯算法 每日一句&#xff1a;人的一生&#xff0c;可以有所作为的时机只有一次&#xff0c;那就是现在&#xff01;&#xff01;&#xff01;&am…

Golang Struct 继承的深入讨论和细节

1&#xff09;结构体可以使用嵌套匿名结构体所有的字段和方法&#xff0c;即&#xff1a;首字母大写或者小写的字段、方法&#xff0c;都可以使用。 package mainimport "fmt"type A struct {Name stringage int }func (a *A) SayName() {fmt.Println("A say …

澳大利亚量子计算制造商SQC获5000万美元投资用于硅量子计算

​&#xff08;图片来源&#xff1a;网络&#xff09; Silicon Quantum Computing&#xff08;SQC&#xff09;是一家总部位于悉尼的量子计算制造商初创公司。澳大利亚联邦政府、联邦银行&#xff08;CBA&#xff09;&#xff0c;新南威尔士大学和Telstra联合向其投入资金超过5…

算法--单链表

算法–单链表 1.合并链表 1.合并两个排序的链表 解法&#xff1a;这个比较容易&#xff0c;直接对比两个两个链表节点&#xff0c;小的节点直接插入到返回的新链表上 /*** struct ListNode {* int val;* struct ListNode *next;* ListNode(int x) : val(x), next(nullptr)…

2023高频前端面试题-TCP

1. TCP 协议是什么? TCP(Transmission Control Protocol 传输控制协议) 是一种面向连接(连接导向) 的、可靠的、 基于 IP 的传输层协议。 TCP 使⽤校验、确认和重传机制来保证可靠传输 而 HTTP 协议 就是建立在 TCP / IP 协议 之上的一种应用。 TCP: 三次握手, 四次挥手~ …

关于本地项目上传到gitee的详细流程

如何上传本地项目到Gitee的流程&#xff1a; 1.Gitee创建项目 2. 进入所在文件夹&#xff0c;右键点击Git Bash Here 3.配置用户名和邮箱 在gitee的官网找到命令&#xff0c;注意这里的用户名和邮箱一定要和你本地的Git相匹配&#xff0c;否则会出现问题。 解决方法如下&…

支付宝证书到期更新完整过程

如果用户收到 支付宝公钥证书 到期通知后&#xff0c;可以根据如下指引更新证书 确认上传成功后就会生成新的证书&#xff0c;把新的证书替换到生产环境就可以了