聊聊 ThreadLocal

news2024/12/27 7:56:55

简介

ThreadLocal 是 Java 提供的一种用于实现线程本地变量的机制。它允许每个线程独立地存储和访问自己的变量副本,从而避免线程之间的共享数据问题,确保线程安全。ThreadLocal 特别适用于存储与线程相关的状态信息,比如用户会话、数据库连接等。它具有如下特点:

  • 每个线程独立的副本:每个线程通过 ThreadLocal 获取的变量副本是独立的,其他线程无法访问或修改。
  • 避免竞争条件:由于每个线程持有自己的数据副本,因此可以避免多线程环境下的竞争条件和同步问题。
  • 内存管理:ThreadLocal 变量在使用完后,应该通过调用 remove() 方法进行清理,以避免内存泄漏。尤其是在使用线程池时,线程可能会被复用,导致旧的线程本地变量仍然存在。
  • ThreadLocal 类提供了一些基本的方法,最常用的方法包括:
    • set(T value):设置当前线程的线程本地变量值。
    • get():获取当前线程的线程本地变量值。
    • remove():删除当前线程的线程本地变量值。
public class ThreadLocalExample {
    // 创建一个 ThreadLocal 变量
    private static ThreadLocal<Integer> threadLocalValue = ThreadLocal.withInitial(() -> 0);

    public static void main(String[] args) {
        Runnable task = () -> {
            // 每个线程都可以独立地设置和获取 ThreadLocal 的值
            int value = threadLocalValue.get();
            System.out.println(Thread.currentThread().getName() + " initial value: " + value);
            threadLocalValue.set(value + 1);
            System.out.println(Thread.currentThread().getName() + " updated value: " + threadLocalValue.get());
        };

        // 创建多个线程
        Thread thread1 = new Thread(task, "Thread-1");
        Thread thread2 = new Thread(task, "Thread-2");

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

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

        // 在主线程中获取 ThreadLocal 的值
        System.out.println("Main thread value: " + threadLocalValue.get());
    }
}

输出示例

Thread-1 initial value: 0
Thread-1 updated value: 1
Thread-2 initial value: 0
Thread-2 updated value: 1
Main thread value: 0

ThreadLocal 是 Java 中一个强大的工具,可以在多线程环境下方便地管理线程局部变量。合理使用 ThreadLocal 可以提高程序的安全性和可维护性,它适合用于少量的、频繁访问的状态数据,对于大型数据对象或需要长时间持有的状态,使用其他机制可能更合适。

底层数据结构

  1. 每个线程都有一个 ThreadLocalMap:
    • 在 Java 的 Thread 类中,包含一个 ThreadLocalMap 类型的成员变量 threadLocals。
    • ThreadLocal 通过这个 ThreadLocalMap 来存储和管理线程独有的变量。
// Thread 类中的定义
ThreadLocal.ThreadLocalMap threadLocals = null;
  1. ThreadLocalMap 的设计:
  • ThreadLocalMap 是 ThreadLocal 的内部静态类,类似于一个定制的 HashMap。
  • 每个键是 ThreadLocal 对象的引用,值是对应线程的本地变量值。

ThreadLocalMap 的关键结构

  1. Entry 类:
  • ThreadLocalMap 的每个键值对是一个 Entry 对象。
  • Entry 的键是弱引用的 ThreadLocal,值是对应的变量值。
static class Entry extends WeakReference<ThreadLocal<?>> {
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}
  • 弱引用的意义:
    • 当一个 ThreadLocal 对象被垃圾回收时,其对应的 Entry.key 会变为 null。
    • 但如果没有及时调用 remove() 方法,Entry.value(即线程变量的值)可能会导致内存泄漏。
  1. 存储方式:
    ThreadLocalMap 使用一个数组作为底层存储结构,与 HashMap 类似,但针对 ThreadLocal 进行了优化。
    数组的索引通过 ThreadLocal 的 hashCode 计算,使用开放地址法解决冲突。
private Entry[] table; // 用于存储键值对
  1. 扩容机制:
    • 当数组中存储的键值对过多,达到一定阈值时,会进行扩容。
    • 在扩容时,还会清理掉 Entry.key 为 null 的无效项。

ThreadLocal 的操作流程

  1. set(T value) 方法:
    • 将当前线程的变量存储到 ThreadLocalMap 中。
    • 如果 ThreadLocalMap 不存在,则初始化一个新的 ThreadLocalMap。
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value); // 存入ThreadLocalMap
    else
        createMap(t, value); // 初始化ThreadLocalMap
}
  1. get() 方法:
  • 从当前线程的 ThreadLocalMap 中获取变量值。
  • 如果 ThreadLocalMap 为 null,则返回默认值(通过 initialValue() 方法设置)。
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            return (T)e.value;
        }
    }
    return setInitialValue(); // 如果不存在,返回默认值
}
  1. remove() 方法:
    • 删除当前线程中存储的变量,避免内存泄漏。
    • 从 ThreadLocalMap 中清除对应的 Entry。
public void remove() {
    ThreadLocalMap map = getMap(Thread.currentThread());
    if (map != null) {
        map.remove(this);
    }
}

内存泄漏问题
ThreadLocal 的内存泄漏问题主要与其 ThreadLocalMap 的设计有关:

  1. ThreadLocal 的键是弱引用:
    • 当一个线程完成任务后,如果 ThreadLocal 没有强引用指向它,ThreadLocal 会被回收。
    • 但是,ThreadLocalMap 的 Entry 中的值是强引用,不会自动清除。
  2. 解决方案:
    • 在使用完 ThreadLocal 后,调用 remove() 方法,显式清理当前线程的变量值。

InheritableThreadLocal

如果需要将父线程的值传递给子线程,可以使用 InheritableThreadLocal。
示例代码:

public class InheritableThreadLocalExample {
    private static InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<>();

    public static void main(String[] args) {
        threadLocal.set("Parent Thread Value");

        Thread childThread = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get());
        });

        childThread.start();
    }
}

输出:

Thread-0: Parent Thread Value

线程池中值传递问题

在 线程池中使用 InheritableThreadLocal 来传递父线程的值给子线程 存在一定的局限性,原因主要在于线程池的线程复用机制。以下是详细的分析:

InheritableThreadLocal 的特点

  1. 父子线程的值传递:
    • InheritableThreadLocal 允许子线程在创建时自动继承父线程的值。
    • 当子线程从父线程创建时,会将父线程中 InheritableThreadLocal 的值复制到子线程中。
  2. 值的传播机制:
    • 在 Thread 的构造方法中,InheritableThreadLocal 会调用其 childValue() 方法,将父线程的值传递到子线程。
    • 默认实现是直接复制父线程的值,可以通过重写 childValue() 来实现定制化。

在线程池中使用的局限性

  1. 线程池复用线程的特性:
    • 线程池中的线程是复用的,并不会每次执行任务时都创建新线程。
    • 因此,InheritableThreadLocal 的值在子线程中只会初始化一次(在线程首次创建时),后续的任务执行时不会自动更新 InheritableThreadLocal 的值。
  2. 父线程与子线程的绑定关系失效:
    • 在线程池中,任务的执行线程不固定,因此无法保证线程的 InheritableThreadLocal 值与提交任务的父线程一致。

解决方案
如果需要在线程池中实现父线程到子线程的数据传递,可以使用以下两种方法:

  1. 手动传递上下文
    手动传递上下文数据到任务中执行:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolWithManualContext {
    private static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        threadLocal.set("Parent Thread Value");

        ExecutorService threadPool = Executors.newFixedThreadPool(2);

        threadPool.submit(() -> {
           // 手动传递上下文
//            String value = threadLocal.get();
//            System.out.println("子线程获取到的值: " + value);

            String value = "Parent Thread Value"; // 手动传递值
            System.out.println("子线程获取到的值: " + value);
        });

        threadPool.shutdown();
    }
}
  1. 使用 TransmittableThreadLocal
    阿里巴巴的开源工具类 TransmittableThreadLocal 是 InheritableThreadLocal 的增强版本,专门为了解决线程池中上下文传递的问题。
  • 核心特性:
    • 它通过在任务提交时拷贝父线程的上下文,确保在线程池中正确传递上下文。

引入依赖
如果使用 Maven,可以添加以下依赖:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>transmittable-thread-local</artifactId>
    <version>2.12.4</version>
</dependency>

示例代码

import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.threadpool.TtlExecutors;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolWithTransmittableThreadLocal {
    private static TransmittableThreadLocal<String> threadLocal = new TransmittableThreadLocal<>();

    public static void main(String[] args) {
        threadLocal.set("Parent Thread Value");

        // 包装线程池
        ExecutorService threadPool = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(2));

        threadPool.submit(() -> {
            // 子线程正确获取父线程的值
            String value = threadLocal.get();
            System.out.println("子线程获取到的值: " + value);
        });

        threadPool.shutdown();
    }
}

输出结果

子线程获取到的值: Parent Thread Value

结论

  1. 直接使用 InheritableThreadLocal:
    • 不适用于线程池环境,因为线程池复用线程的特性会导致值的传递失效。
  2. 推荐使用 TransmittableThreadLocal:
    • 如果需要在线程池中安全且有效地传递上下文,建议使用 TransmittableThreadLocal。
  3. 手动传递上下文:
    • 对于简单场景,可以通过显式传递参数来解决上下文问题,但需要开发者管理传递逻辑。

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

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

相关文章

音视频入门知识(二)、图像篇

⭐二、图像篇 视频基本要素&#xff1a;宽、高、帧率、编码方式、码率、分辨率 ​ 其中码率的计算&#xff1a;码率(kbps)&#xff1d;文件大小(KB)&#xff0a;8&#xff0f;时间(秒)&#xff0c;即码率和视频文件大小成正比 YUV和RGB可相互转换 ★YUV&#xff08;原始数据&am…

论文研读:AnimateDiff—通过微调SD,用图片生成动画

1.概述 AnimateDiff 设计了3个模块来微调通用的文生图Stable Diffusion预训练模型, 以较低的消耗实现图片到动画生成。 论文名&#xff1a;AnimateDiff: Animate Your Personalized Text-to-Image Diffusion Models without Specific Tuning 三大模块&#xff1a; 视频域适应…

百度地图绘制行政区域及设置中心点

// 画行政区 const drwaDistrict (disList, clear true) > {clear && clearOverlays();if (!bMapGL.value) return;let bd new bMapGL.value.Boundary();disList.map((item) > {bd.get(item, function (rs1) {// rs1 是行政区对应的点集合 此处非每次必反信息…

jetson Orin nx + yolov8 TensorRT 加速量化 环境配置

参考【Jetson】Jetson Orin NX纯系统配置环境-CSDN博客 一 系统环境配置&#xff1a; 1.更换源&#xff1a; sudo vi /etc/apt/sources.list.d/nvidia-l4t-apt-source.list2.更新源&#xff1a; sudo apt upgradesudo apt updatesudo apt dist-upgrade sudo apt-get updat…

VirtualBox下ubuntu23.04使用主机串口以及使用 minicom 进行串口调试

VirtualBox下ubuntu23.04使用主机串口以及使用 minicom 进行串口调试 一、打开设备管理器看主机&#xff08;Window系统&#xff09;是否识别出串口&#xff0c;我这边显示的串行通信端口是COM3 二、打开VirtualBox&#xff0c;设置串口和USB设备 串口设置&#xff1a; 启用…

Python 自动化 打开网站 填表登陆 例子

图样 简价&#xff1a; 简要说明这个程序的功能&#xff1a; 1. **基本功能**&#xff1a; - 自动打开网站 - 自动填写登录信息&#xff08;号、公司名称、密码&#xff09; - 显示半透明状态窗口实时提示操作进度 2. **操作流程**&#xff1a; - 打开网站后自动…

本原多项式

将 G F ( p ) GF(p) GF(p)延伸为有 p m p^m pm个元素的域&#xff0c;称之为 G F ( p ) GF(p) GF(p)的扩域&#xff0c;表示为 G F ( p m ) GF(p^m) GF(pm). G F ( p ) GF(p) GF(p)是 G F ( p m ) GF(p^m) GF(pm)的子集。 G F ( p m ) GF(p^m) GF(pm)元素个数为 p m p^m pm。 …

【物联网技术与应用】实验15:电位器传感器实验

实验15 电位器传感器实验 【实验介绍】 电位器可以帮助控制Arduino板上的LED闪烁的时间间隔。 【实验组件】 ● Arduino Uno主板* 1 ● 电位器模块* 1 ● USB电缆*1 ● 面包板* 1 ● 9V方型电池* 1 ● 跳线若干 【实验原理】 模拟电位器是模拟电子元件&#xff0c;模…

能省一点是一点 - 享元模式(Flyweight Pattern)

享元模式&#xff08;Flyweight Pattern&#xff09; 享元模式&#xff08;Flyweight Pattern&#xff09;享元模式&#xff08;Flyweight Pattern&#xff09;概述享元模式包含的角色&#xff1a;享元模式应用场景 talk is cheap&#xff0c; show you my code总结 享元模式&a…

学习C++:变量

变量&#xff1a; 作用&#xff1a;给一段指定的内存空间起名&#xff0c;方便操作这段内容 &#xff08;变量存在的意义&#xff1a;方便我们管理内存空间&#xff09; 语法&#xff1a;数据类型 变量名 初始值&#xff1b; 实例&#xff1a;

蓝桥杯物联网开发板硬件组成

第一节 开发板简介 物联网设计与开发竞赛实训平台由蓝桥杯大赛技术支持单位北京四梯科技有限公司设计和生产&#xff0c;该产品可用于参加蓝桥杯物联网设计与开发赛道的竞赛实训或院校相关课程的 实践教学环节。 开发板基于STM32WLE5无线微控制器设计&#xff0c;芯片提供了25…

Day35汉明距离

两个整数之间的 汉明距离 指的是这两个数字对应二进制位不同的位置的数目。 给你两个整数 x 和 y&#xff0c;计算并返回它们之间的汉明距离。 class Solution {public int hammingDistance(int x, int y) {int cnt 0;while (Math.max(x, y) ! 0) {if ((x & 1) ! (y &…

matplotlib pyton 如何画柱状图,利用kimi,直接把图拉倒上面,让他生成

要绘制类似于您提供的图像的柱状图&#xff0c;您可以使用Python中的Matplotlib库&#xff0c;这是一个非常流行的绘图库。以下是一个简单的示例代码&#xff0c;展示如何使用Matplotlib来创建一个类似的柱状图&#xff1a; python import matplotlib.pyplot as plt import nu…

计算机网络——期末复习(3)4-6章考试重点

第四章 根据IPv4第1个十进制数值判断&#xff0c;127以下为A类&#xff0c;128~191为B类&#xff0c;192~223为C类不能分配给主机或路由器接口的&#xff1a;A类网络号0和127&#xff0c;主机号全为0或全为1私有地址&#xff08;Private IP Address&#xff09;是指一类专门保…

【安全编码】Web平台如何设计防止重放攻击

我们先来做一道关于防重放的题&#xff0c;答案在文末 防止重放攻击最有效的方法是&#xff08; &#xff09;。 A.对用户密码进行加密存储使用 B.使用一次一密的加密方式 C.强制用户经常修改用户密码 D.强制用户设置复杂度高的密码 如果这道题目自己拿不准&#xff0c;或者…

【WebAR-图像跟踪】在Unity中基于Imagine WebAR实现AR图像识别

写在前面的话 感慨一下&#xff0c; WebXR的发展是真的快&#xff0c;20年的时候&#xff0c;大多都在用AR.js做WebAR。随着WebXR标准发展&#xff0c;现在诸如Threejs、AFrame、Unity等多个平台都支持里WebXR。 本文将介绍在Unity中使用 Image Tracker实现Web端的AR图像识别功…

HTML5实现好看的圣诞节网站源码

HTML5实现好看的圣诞节网站源码 前言一、设计来源1.1 主界面1.2 圣诞节由来界面1.3 圣诞活动界面1.4 圣诞活动门票界面1.5 团队介绍界面1.6 圣诞照片墙界面1.7 圣诞留言界面1.8 圣诞趣事界面1.9 联系我们界面 二、效果和源码2.1 动态效果2.2 源代码 源码下载结束语 HTML5实现好…

go下载依赖提示连接失败

1、现象 Go下载模块提示连接失败 dial tcp 142.251.42.241:443: connectex: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.…

分布式事务入门 一

分布式事务入门 一 您好&#xff0c;我是今夜写代码,今天学习下分布式事务相关理论&#xff0c;以及常见的解决方案&#xff0c;为后续掌握Seata分布式事务框奠定基础。 为什么需要分布式事务? 分布式事务主要由于存储资源的分布性&#xff0c;通常涉及多个数据库。 分布式…

VSCode设置Playwright教程

1.安装扩展 打开VS Code&#xff0c;在扩展—>搜索"Playwright Test for VSCode"&#xff0c;点击安装 按快捷键CommandShiftP&#xff0c;输入install playwright&#xff0c;点击安装Playwright 安装成功会有如下提示 2.调试脚本 打开tests/example.spec.ts文…