ThreadLocal —— 让每个线程拥有自己的变量副本

news2024/9/24 9:26:57

在多线程编程中,数据共享是一个常见的话题。很多时候我们需要多个线程共享同一个数据,以实现数据的一致性和通信。然而,在一些场景下,我们并不希望数据被多个线程共享,而是希望每个线程都拥有自己独立的一份数据。这时,ThreadLocal 就可以派上用场了。

1.ThreadLocal是什么?

ThreadLocal 是 Java 中的一个类,它提供了线程本地变量的功能。简单来说,就是为每一个使用该变量的线程提供了一个独立的变量副本。这样一来,每个线程都可以独立地改变自己的副本,而不会影响到其他线程。

2.ThreadLocal的使用方法

2.1常用方法

// 创建ThreadLocal对象
public ThreadLocal() {}
// 设置当前线程绑定的局部变量
public void set(T value) {}
// 获取当前线程绑定的局部变量
public T get() {}
// 删除当前线程绑定的局部变量
public void remove() {}

2.2使用案例

下面是一个简单的示例,展示了如何使用 ThreadLocal

public class ThreadLocalExample {
    static ThreadLocal<Integer> localVariable = new ThreadLocal<>();


    public static void main(String[] args) {
        Runnable task = () -> {
            // 设置当前线程的localVariable值
            localVariable.set(Thread.currentThread().getId());


            System.out.println("线程 " + Thread.currentThread().getId() + " 的局部变量: " + localVariable.get());
        };


        Thread thread1 = new Thread(task);
        Thread thread2 = new Thread(task);


        thread1.start();
        thread2.start();


        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }


        System.out.println("主线程的局部变量: " + localVariable.get());
    }
}

在这个例子中,每个线程都有一个 localVariable 的独立副本,它们互不影响

3.ThreadLocal的内部结构

ThreadLocal 类的内部结构比较简单。它主要由两个部分组成:

1. ThreadLocal 对象本身,持有每个线程的引用。

  1. Thread 对象中有一个 ThreadLocalMap,用于存储线程与 ThreadLocal 变量之间的映射关系。

当调用 ThreadLocalget() 方法时,会根据当前线程找到对应的 ThreadLocalMap 并返回相应的值。如果调用 set() 方法,则会在当前线程的 ThreadLocalMap 中设置一个键值对。

4.核心方法及源码分析

ThreadLocal 提供了几个常用的方法:

  • public T get(): 获取当前线程的变量副本。
  • public void set(T value): 设置当前线程的变量副本。
  • public void remove(): 移除当前线程的变量副本。

4.1 set方法

public void set(T value) {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 获取线程绑定的ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null) { 
       // 不为空直接赋值
        map.set(this, value);
    } else {
      // 为空则创建Map
      // 将当前线程t作为key,将value作为值,存放ThreadLocalMap中
        createMap(t, value);
    }
}

4.2 get方法

public T get() {
    // 获取当前线程对象
    Thread t = Thread.currentThread();
    // 获取当前线程关联的ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    // 如果map存在
    if (map != null) {
        // this是当前ThreadLocal
        // 以当前ThreadLocal为key,获取存放的entry对象
        ThreadLocalMap.Entry e = map.getEntry(this);
        // 获取到存放的值,直接返回
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    // 进行初始化操作
    // 1.如果map不存在,则表示当前线程没有关联ThreadLocalMap
    // 2.如果e不存在,则当前ThreadLocal没有关联entry
    return setInitialValue();
}


// 初始化方法
private T setInitialValue() {
    // 获取空值
    T value = initialValue();
    // 获取当前线程对象
    Thread t = Thread.currentThread();
    // 获取当前线程关联的ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    // map不为空,直接赋值
    if (map != null) {
        map.set(this, value);
    } else {
    // map不存在,创建map并赋值
        createMap(t, value);
    }
    // 判断是否是该类型TerminatingThreadLocal
    if (this instanceof TerminatingThreadLocal) {
        // 注册
        TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
    }
    // 返回值
    return value;
}

4.3 remove方法

public void remove() {
   // 获取当前线程绑定的ThreadLocalMap
   ThreadLocalMap m = getMap(Thread.currentThread());
   if (m != null) {
       // 不为空,删除key对应的entry实体
       m.remove(this);
   }
}

5. ThreadLocalMap源码分析

5.1 存储结构Entry

// ThreadLocalMap中存放数据的键值对
// 继承WeakReference(弱引用)
static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;  // 具体存放的值
    // 构建Entry对象  key为弱引用
    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

5.2 构造函数

 // 初始阈值16
 private static final int INITIAL_CAPACITY = 16;
 // ThreadLocalMap中存放的Entry对象
 private Entry[] table;
 // Entry数组的长度
 private int size = 0;
 
 // firstKey:entry中的key  
 // firstValue:entry中的value
 ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
     // 初始化Entry数组,容量是16
    table = new Entry[INITIAL_CAPACITY];
    // 通过key进行hash运算,获取存放的下标
    int i = firstKey.threadLocalHashCode &(INITIAL_CAPACITY - 1);
    // 初始化entry,并设置key、value
    table[i] = new Entry(firstKey, firstValue);
    // 设置初始大小
    size = 1;
    // 设置阈值参数
    setThreshold(INITIAL_CAPACITY);
}


// 设置调整阈值,用于扩展table时使用
private void setThreshold(int len) {
    threshold = len * 2 / 3;
}

5.3 ThreadLocalMap.set()方法

private void set(ThreadLocal<?> key, Object value) {
    // ThreadLocalMap中存放数据的Entry数组
    Entry[] tab = table;
    // 数组长度
    int len = tab.length;
    // 对key进行hash运算,获取存放下标
    int i = key.threadLocalHashCode & (len-1);


    // 1.tab[i]不为空,直接替换value结束循环
    /** 2.tab[i]不为空,出现hash冲突,
     * 则执行 e = tab[i = nextIndex(i, len)],
       去i+1下标 **/ 
    // 使用线性探测法查找元素
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
         // key是e的引用对象,则返回true
        if (e.refersTo(key)) {
            e.value = value;
            return;
        }
        // 如果e为空,则代表之前的ThreadLocal被回收了
        if (e.refersTo(null)) {
            // 用新元素替换旧元素,防止内存泄漏
            replaceStaleEntry(key, value, i);
            return;
        }
    }
    // 构建新的entry对象,存放tab[i]
    tab[i] = new Entry(key, value);
    // 个数累加
    int sz = ++size;
    /**
   * cleanSomeSlots用于清除那些e.get()==null的元素,
   * 这种数据key关联的对象已经被回收,所以这个Entry(table[index])可以被置null。
   * 如果没有清除任何entry,并且当前已达到了扩容(长度的2/3),那么进行rehash(执行一次全表的扫描清理工作)
   */
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

5.4 关于ThreadLocal出现内存泄露原因

笔者看过较多的答案都是说ThreadLocalMap的key是虚引用,gc执行后key引用的对象回收,导致内存泄漏。但是,如果ThreadLocalMap的key改成强引用,那么gc执行后,key的引用仍然存在,仍然无法进行回收,内存泄露仍然存在。根本原因在于Entry中的value没有销毁

笔者认为真正解决的办法应该是如下两点:

  1. 在使用完ThreadLocalMap中存放的变量后,要手动remove
  2. 等待线程执行完毕

6.总结

通过本文的介绍,我们了解到了 ThreadLocal 的概念、作用以及其实现原理。ThreadLocal 不仅简化了多线程编程中的数据管理,还能提高程序的并发性能。

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

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

相关文章

【C++类和对象(中)】—— 我与C++的不解之缘(四)

前言&#xff1a; 接下来进行类和对象中的学习&#xff0c;了解类和对象的默认成员函数 一、类和对象默认成员函数 默认成员函数就是用户没有显示实现&#xff0c;编译器会自动生成的成员函数。 一个类&#xff0c;我们不显示实现的情况下&#xff0c;编译器就会默认生成一下留…

无人机开启农林植保新篇章

嘿&#xff0c;小伙伴们&#xff0c;你们知道吗&#xff1f;无人机已经悄悄在农业领域大展拳脚&#xff0c;成为现代农业的“黑科技”新宠儿啦&#xff01; 想象一下&#xff0c;广袤的田野上空&#xff0c;无人机如同勤劳的蜜蜂&#xff0c;精准高效地完成着各项任务&#xff…

unity 使用 compute shader的步骤

这里详细的记载使用步骤&#xff0c;我这个例子是让一个立方体上下不停的动 创建一个compute shader 一个普通shader 一个材质 一个C# 先挨个写上类容 这里kernel 指定main函数入口&#xff0c;RWStructuredBuffer就是数组&#xff0c;具体size是在外部指定的&#xff0c;可能…

C#使用CEFSharp获取动态网页源码

CEF 全称是Chromium Embedded Framework&#xff08;Chromium嵌入式框架&#xff09;&#xff0c;是个基于Google Chromium项目的开源Web browser控件&#xff0c;支持Windows, Linux, Mac平台。CEFSharp就是CEF的C#移植版本。 访问以下链接可以获取CEF的详细介绍 chromiumem…

c++版opencv长文指南

c版opencv长文指南 1、配置opencv库1.1 下载1.2 配置1.2.1 配置包含目录1.2.2 配置库含目录1.2.3 配置链接器1.2.4 配置系统环境变量 2、学习路线3、入门知识3.1 图像读取与显示3.2 图像色彩空间转换3.2 图像对象的创建与赋值3.2.1 图像对象的创建3.2.2 图像对象的赋值 3.3 图像…

轻松搞定小程序生成短链接/二维码,你学会了吗?

朋友们&#xff0c;大家有没有遇到过这样的困扰&#xff1a;小程序由于不是链接&#xff0c;在短信或者其他平台里根本没法推广&#xff0c;导致小程序的用户量很难涨起来。 那小程序转成链接真的就没办法实现吗&#xff1f;当然不是&#xff01; 现在有一款超实用的工具——…

zigbee笔记:十、ZStack(2.3.0-1.4.0)的OSAL使用分析

zigbee笔记&#xff1a;九中&#xff0c;我们已经学会了利用模板&#xff0c;定制自己的个性开发工程&#xff0c;本文为协议栈&#xff08;ZStack-CC2530-2.3.0-1.4.0&#xff09;代码使用分析笔记&#xff0c;来进一步掌握协议栈的使用。 一、协议栈使用知识点 1、协调器、路…

python从入门到精通:基础语法讲解

1、字面量 字面量&#xff1a;在代码中&#xff0c;被写下来的固定的值&#xff0c;称之为字面量。 python中常用的几种数据类型&#xff1a; 类型描述说明数字&#xff08;Number&#xff09; 整数&#xff08;int&#xff09; 浮点数&#xff08;float&#xff09; 复数&a…

Graph-Cot:图上迭代推理

Graph-Cot&#xff1a;图上迭代推理 提出背景GRAPH-COT 对比 MindMapGRAPH-COT 和 MindMap 多链推理方法结合案例一&#xff1a;复杂症状的诊断案例二&#xff1a;罕见病的诊断案例三&#xff1a;治疗方案的制定 解法拆解目的问题解法 GRAPH-COT 医学问诊 论文&#xff1a;http…

(BO)Bayes-CNN多变量时序预测 基于贝叶斯算法-卷积神经网络多变量时序预测(多输入单输出)Matlab代码

Bayes-CNN多变量时序预测 基于贝叶斯算法-卷积神经网络多变量时序预测&#xff08;多输入单输出&#xff09;Matlab代码 程序已经调试好&#xff0c;无需更改代码替换数据集即可运行&#xff01;&#xff01;&#xff01;数据格式为excel&#xff01;(如下) 需要其他的都可以…

CSS3下拉菜单实现

导航菜单&#xff1a; <nav class"multi_drop_menu"><!-- 一级开始 --><ul><li><a href"#">Power</a></li><li><a href"#">Money</a></li><li><a href"#"…

【数模修炼之旅】02 多目标规划 深度解析(教程+代码)

【数模修炼之旅】02 多目标规划 深度解析&#xff08;教程代码&#xff09; 接下来 C君将会用至少30个小节来为大家深度解析数模领域常用的算法&#xff0c;大家可以关注这个专栏&#xff0c;持续学习哦&#xff0c;对于大家的能力提高会有极大的帮助。 1 多目标规划介绍及应…

百度智能云发布3款轻量级+2款场景大模型

文心大模型ERNIE 3.5是目前百度智能云千帆大模型平台上最受欢迎的基础大模型之一。针对用户的常见通用的对话场景&#xff0c;ERNIE 3.5 在指令遵循、上下文学习和逻辑推理能力三方面分别进行了能力增强。 ERNIE Speed作为三款轻量级大模型中的“大个子”&#xff0c;推理场景…

解决连接不上Linux和服务器中的Nacos(Windows中能连接但是Linux中却不行)

报错 com.alibaba.nacos.shaded.io.grpc.StatusRuntimeException: UNKNOWN: Uncaught exception in the SynchronizationContext. Re-thrown. at com.alibaba.nacos.shaded.io.grpc.Status.asRuntimeException(Status.jav 2024-08-13T10:21:52.93708:00 ERROR 27764 --- …

ArduPilot开源代码之FMU+IOMCU设计

ArduPilot开源代码之FMUIOMCU设计 1. 源由2. 设计概念3. FMU & IOMCU特点3.1 FMU&#xff08;Flight Management Unit&#xff09;的主要功能3.2 IOMCU&#xff08;Input/Output Microcontroller Unit&#xff09;的主要功能3.3 主要差异 4. 主/辅助(MAIN/AUX) PWM输出5. 软…

【北京仁爱堂】痉挛性斜颈的早期症状,你了解吗?

在日常生活中&#xff0c;您可能很少听到“痉挛性斜颈”这个名词&#xff0c;但它却是一种不容忽视的疾病。今天&#xff0c;就让我们一起来了解一下痉挛性斜颈的早期症状&#xff0c;以便能够及时发现并采取相应的措施。 痉挛性斜颈是一种局限性肌张力障碍疾病&#xff0c;主要…

Gitlab搭建服务器好做吗 Gitlab搭建服务器操作指南

GitLab是一个强大的开源代码托管和CI/CD工具&#xff0c;广泛用于软件开发的版本控制和自动化构建。对于许多公司和开发团队来说&#xff0c;自行搭建GitLab服务器是一个既具挑战性又有很多好处的选择。本文将详细讨论搭建GitLab服务器的难易程度&#xff0c;提供一份详尽的操作…

死信队列.

“死信”是指在RabbitMQ中那些因为某些原因无法被正常处理的消息。

OpenCV图像滤波(11)中值滤波medianBlur函数的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 功能描述 该函数使用中值滤波器对图像进行模糊处理。 该函数采用 ksizeksize 的窗口尺寸对图像进行平滑处理。对于多通道图像&#xff0c;每个通道将被独…

Linux发行版深度对比:Ubuntu、CentOS与Fedora

在Linux的广阔世界中&#xff0c;Ubuntu、CentOS和Fedora作为三大主流发行版&#xff0c;各自拥有独特的生态系统、用户基础和开发理念。它们不仅在技术架构上有着显著的差异&#xff0c;更在用户体验、社区支持、软件更新策略以及安全性能等方面展现出各自的特色。本文将对这三…