首页 > 其他分享 >深入理解 ThreadLocal:机制、原理与实践

深入理解 ThreadLocal:机制、原理与实践

时间:2024-08-14 20:24:29浏览次数:7  
标签:Thread ThreadLocalMap threadLocalValue 实践 value ThreadLocal 线程 原理

引言

ThreadLocal 是 Java 中一个非常重要的工具,广泛用于解决多线程环境下变量共享的问题。然而,ThreadLocal 的使用也可能带来一些隐患,尤其是在结合线程池的场景中,可能导致数据混乱。本文将深入探讨 ThreadLocal 的工作机制及其可能带来的问题,并给出相应的解决方案。

一、ThreadLocal 基础概念

ThreadLocal 为每个线程提供了一个独立的变量副本,这样每个线程都可以独立修改自己的副本,而不会影响其他线程的副本。每个 Thread 实例都持有一个 ThreadLocalMap 对象,用于存储 ThreadLocal 变量。

ThreadLocalMap 中,ThreadLocal 实例作为键(key),而变量值作为值(value)。需要注意的是,ThreadLocalMap 中的键是由 ThreadLocal 实例的弱引用(WeakReference)来保存的。

  • 在之前因为竞态条件,导致线程之间进行临界值的写操作出现数据错乱、不一致的情况。

  • ThreadLocal是一种旨在根源上解决线程安全问题,出现线程安全问题是因为有静态条件的出现,每一个线程本地都会有一个Entry数组来存储值。

  • ThreadLocalMap是什么?

ThreadLocalMapThreadLocal 内部使用的一个数据结构,用来存储每个线程所对应的 ThreadLocal 变量的值。每个线程都会有一个独立的 ThreadLocalMap 实例,这个 ThreadLocalMap 保存在线程的私有成员变量中。

简单理解:其实ThreadLocalMap就是一个Entry数组,用来存储当前线程的所设置的threadlocal的值。

二、ThreadLocal 的set方法做了什么?

  • 一图胜万语

  •  源码

获取当前线程,并传递给内置的set方法

 通过当前线程执行getMap()方法,创建一个ThreadLocalMap。

判断map存在后,通过map.set方法,把value设置进新创建的ThreadLocalMap;可以看到key为当前ThreadLocal实例,值为传入的实际Value。

 三、ThreadLocal 的简单使用

public class ThreadLocalExample {
    // 创建一个ThreadLocal对象,用于存储每个线程独立的Integer值,初始值为0
    private static ThreadLocal<Integer> threadLocalValue = ThreadLocal.withInitial(() -> 0);

    public static void main(String[] args) {
        // 定义一个Runnable任务,模拟每个线程对ThreadLocal变量的操作
        Runnable task = () -> {
            // 从ThreadLocal中获取当前线程的值,初始时该值为0
            int value = threadLocalValue.get();
            // 对该值进行自增操作
            value++;
            // 将自增后的值重新存入ThreadLocal中
            threadLocalValue.set(value);
            // 输出当前线程的名称和ThreadLocal中的值
            System.out.println(Thread.currentThread().getName() + ": " + threadLocalValue.get());
        };

        // 创建两个线程,执行相同的任务
        Thread thread1 = new Thread(task, "Thread 1");
        Thread thread2 = new Thread(task, "Thread 2");

        // 启动两个线程
        thread1.start();
        thread2.start();
    }
}
  • 每个线程操作的都是自己独立的 ThreadLocal 副本,因此即使它们对 ThreadLocal 值进行了自增操作,彼此之间也没有影响。 
  • 看到下面运行的结果,因为每个线程都有自己的ThreadLocal副本,所以自增完两个都是1

四、ThreadLocal 的内存泄漏问题

由于 ThreadLocal 的键是弱引用,当 ThreadLocal 实例没有强引用指向它时,GC 会自动回收这个键,但与之对应的值则不会自动回收,从而可能导致 ThreadLocalMap 中存在大量键为 null 的条目(Entry)。这些条目如果不及时清理,可能会引发内存泄漏。

  • 通过源码我们可以发现key已经被弱引用给包裹

  • Java 提供了一些机制来防止这种内存泄漏:在 getsetremove 方法中,ThreadLocal 都会自动清理这些键为 null 的条目。 

五、ThreadLocal 在线程池中的问题

在使用线程池时,线程是被复用的,这意味着一个线程在不同任务中可能会复用同一个 ThreadLocal 实例。如果我们不在任务结束后及时清理 ThreadLocal 中的数据,可能会导致下一个任务获取到上一个任务的数据,导致数据混乱。

六、最佳实践:及时清理 ThreadLocal

为了避免在使用 ThreadLocal 时出现内存泄漏或数据混乱,建议在每次使用完 ThreadLocal 后,调用 remove 方法以清理数据。这不仅有助于防止内存泄漏,也能避免线程池中复用 ThreadLocal 导致的数据混乱问题。

try {
    threadLocal.set(value);
    // 执行任务
} finally {
    threadLocal.remove();
}
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadLocalExampleWithThreadPoolExecutor {
    // 创建一个ThreadLocal对象,用于存储每个线程独立的String值
    private static ThreadLocal<String> threadLocalValue = new ThreadLocal<>();

    public static void main(String[] args) {
        // 使用ThreadPoolExecutor创建一个线程池,核心线程数为2,最大线程数为4,队列容量为10
        ThreadPoolExecutor executorService = new ThreadPoolExecutor(
            2,                         // 核心线程数
            4,                         // 最大线程数
            60L,                       // 空闲线程存活时间
            TimeUnit.SECONDS,          // 存活时间的单位
            new LinkedBlockingQueue<>(10) // 任务队列
        );

        // 定义第一个任务,将一个值存入ThreadLocal中
        Runnable task1 = () -> {
            try {
                // 在当前线程中设置ThreadLocal的值
                threadLocalValue.set("Task 1 value");
                System.out.println(Thread.currentThread().getName() + ": " + threadLocalValue.get());
            } finally {
                // 任务完成后,清理ThreadLocal,防止线程复用时出现数据污染
                threadLocalValue.remove();
            }
        };

        // 定义第二个任务,也将一个值存入ThreadLocal中
        Runnable task2 = () -> {
            try {
                // 在当前线程中设置ThreadLocal的值
                threadLocalValue.set("Task 2 value");
                System.out.println(Thread.currentThread().getName() + ": " + threadLocalValue.get());
            } finally {
                // 任务完成后,清理ThreadLocal,防止线程复用时出现数据污染
                threadLocalValue.remove();
            }
        };

        // 提交多个任务到线程池执行
        executorService.submit(task1);
        executorService.submit(task2);

        // 关闭线程池
        executorService.shutdown();
    }
}

结语

ThreadLocal 是一个功能强大的工具,能帮助我们在多线程环境中保持线程安全的数据独立性。然而,它的使用也需要谨慎,尤其是在结合线程池时更要小心处理。通过本文的介绍,希望能帮助你更好地理解 ThreadLocal 的工作原理,并在实际开发中合理使用它。

欢迎指正不足之处,互相学习交流,主要分享一点所学的感想!

标签:Thread,ThreadLocalMap,threadLocalValue,实践,value,ThreadLocal,线程,原理
From: https://blog.csdn.net/m0_64022678/article/details/141198886

相关文章

  • [天线原理及设计>基本原理] 3. 辐射方向图或天线方向图
    《Antenna_Theory_Analysis_and_Design_3rd_Constantine_A._Balanis.pdf》3.辐射方向图或天线方向图天线辐射方向图或天线方向图(radiationpatternorantennapattern)被定义为“天线辐射特性随空间坐标变化的数学函数或图形表示。在大多数情况下,辐射模式是在远场区域确定......
  • 通义灵码:AI 研发趋势与效果提升实践丨SDCon 全球软件技术大会演讲全文整理
    作者:张昕东大家好,我是来自阿里云通义灵码团队的张昕东。很高兴和各位同仁做这次分享,分享的主题是人机协同趋势与效果提升实践。我们所做的模型提升和功能开发是为了促进人机在研发领域的协同,而当今的人机协同现状又决定了我们应该做哪些效果优化。因此,我这次分享主要会从三个方......
  • 通义灵码:AI 研发趋势与效果提升实践丨SDCon 全球软件技术大会演讲全文整理
    作者:张昕东大家好,我是来自阿里云通义灵码团队的张昕东。很高兴和各位同仁做这次分享,分享的主题是人机协同趋势与效果提升实践。我们所做的模型提升和功能开发是为了促进人机在研发领域的协同,而当今的人机协同现状又决定了我们应该做哪些效果优化。因此,我这次分享主要会从三个方......
  • https原理
    目录一、HTTPS的实现原理1.证书验证阶段:2.数据传输阶段:二、为什么数据传输是用对称加密?三、为什么需要CA认证机构颁发证书?1.过程原理如下:四、浏览器是如何确保CA证书的合法性?1.证书包含什么信息?2.证书的合法性依据是什么?3.浏览器如何验证证书的合法性?4.只有认证机......
  • 一、Tomcat基础知识与运行原理
    本章节为介绍如何安装Tomcat工具以及其主要架构知识概念,深入浅出让新人玩家理解为什么选择该容器以及该容器的优点web服务器概念服务器:安装了服务器软件的计算机服务器软件:接收用户的请求,处理请求,做出响应web服务器软件:接收用户的请求,处理请求,做出响应。在web服务器软件......
  • 数据库服务器运维最佳实践
    数据库服务器运维是确保数据库系统高效、稳定和安全运行的关键环节。随着信息技术的不断发展,数据库系统的规模和复杂性不断增加,对运维工作的要求也越来越高。以下将从硬件选择、操作系统和文件系统优化、数据库版本选择、参数优化、数据备份与恢复、性能监控与调优、安全管......
  • 白话双向套利原理
    在A,B两个交易所开立账户,一个执行买入,另一个执行卖出,就好比从便宜的地方进货,到贵的地方卖出,朴素的生意原理。 具体流程上,分为监听、判定、交易、提现、结算这几个步骤。1、监听到某一时刻,判定价差出现套利机会A所:1BTC价格10000USD20000USDTB所:1BTC 9000USD20000USDT ......
  • 科普文:Java基础系列之【java框架基础:字节码增强技术框架ASM#ClassReader实现原理及源
    1概述ASM是Java中比较流行的用来读写字节码的类库,用来基于字节码层面对代码进行分析和转换。在读写的过程中可以加入自定义的逻辑以增强或修改原来已编译好的字节码,比如CGLIB用它来实现动态代理。ASM被设计用于在运行时对Java类进行生成和转换,当然也包括离线处理。ASM短小精......
  • Session的工作原理、Session与Token的区别
    Session是一种在无状态的HTTP协议中用来实现用户状态管理的机制。它通过在服务器端保存用户的状态信息,并通过客户端在每次请求时传递一个唯一的标识符(通常称为SessionID),实现了在多个请求之间维持用户的会话状态。一、Session的工作原理:客户端发送请求:用户首次访问网站......
  • [天线原理及设计>基本原理] 2. 细线天线上的电流分配
    2.细线天线上的电流分配为了说明线性偶极子上电流分布的产生及其随后的辐射,让我们首先从无损双线传输线的几何形状开始,如图1.15(a)所示。电荷的运动沿每条导线产生幅度为I0/2的行波电流。当电流到达每根电线的末端时,它会经历完全反射(幅度相等,相位反转180°)。当反射行波与入射行......