《深入解析 Java 中的 ThreadLocal》

news2024/9/25 10:45:17

ThreadLocal

1.概述

ThreadLocal被称为线程局部变量,用于在线程中保存数据。由于在ThreadLocal中保存的数据仅属于当前线程,所以该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。

ThreadLocal用于在同一个线程间,在不同的类和方法之间共享数据的的场景,也可以用于在不同线程间隔离数据的场景。

ThreadLocal利用Thread中的ThreadLocalMap来进行数据存储。

image-20240920113630852

示例:

public class Test {
	// 线程局部变量
	public static ThreadLocal<String> threadLocal = new ThreadLocal<String>();
	public static void main(String[] args) {
		Thread t1 = new Thread(new Runnable() {
 			@Override
 			public void run() {
 				threadLocal.set("妲己");
 				show();
 				Sample.dosth();
 			}
 		},"线程1");
 		Thread t2 = new Thread(new Runnable() {
 			@Override
 			public void run() {
 				threadLocal.set("后羿");
 				show();
 				Sample.dosth();
 			}
 		},"线程2");
 		t1.start();
 		t2.start();
 	}
 	public static void show() {
 		String role = threadLocal.get();
 		System.out.println("show:"+Thread.currentThread().getName()+"分配角色:" + role);
 	}
}
class Sample {
 	public static void dosth() {
 		String role = Test.threadLocal.get();
 		System.out.println("dosth:"+Thread.currentThread().getName()+"分配角色:" + role);
 	}
}
2.常用方法

1.存储数据至当前线程的ThreadLocalMap:public void set(T value)

public void set(T value) {
     // 获取当前线程
     Thread t = Thread.currentThread();
 	// 获取当前线程的ThreadLocalMap
     ThreadLocalMap map = getMap(t);
 	// 存储数据至当前线程的ThreadLocalMap中
 	// 使用ThreadLocal对象做key,保存数据value
     if (map != null)
         map.set(this, value);
     else
         createMap(t, value);
}

2.从当前线程的ThreadLocalMap中获取数据:public T get()

public T get() {
     // 获取当前线程的ThreadLocalMap
     Thread t = Thread.currentThread();
     ThreadLocalMap map = getMap(t);
     // 使用ThreadLocal对象做key,获取数据(Entry类型)
     if (map != null) {
         ThreadLocalMap.Entry e = map.getEntry(this);
         if (e != null) {
             @SuppressWarnings("unchecked")
             T result = (T)e.value;
             return result;
         }
     }
     return setInitialValue();
}

3.从当前线程的ThreadLocalMap中删除数据:public void remove()

在线程池的线程复用场景中,线程执行完毕时一定要调用remove(),避免在线程被重新放入线程池中时被本地变量的旧状态仍然被保存。

public void remove() {
      // 获取当前线程的ThreadLocalMap
      ThreadLocalMap m = getMap(Thread.currentThread());
      // 使用当前ThreadLocal对象做key,删除数据
      if (m != null)
          m.remove(this);
}

ThreadLocal的get()方法、set()方法和remove()方法,其实最终操作的都是ThreadLocalMap类中的数据。

3. ThreadLocalMap内部结构

ThreadLocalMap内部数据结构是一个Entry类型的数组。每个Entry对象的key为ThreadLocal对象,value为存储的数据。

static class ThreadLocalMap {
   static class Entry extends WeakReference<ThreadLocal<?>> {
       Object value;
       Entry(ThreadLocal<?> k, Object v) {
           super(k);
           value = v;
       }
   }
   ...
   private Entry[] table;
   ...
}

fcde6707ba95014de44f298ac1ff7eb

4.为什么用ThreadLocal做key?

如果在应用中,一个线程中只使用了一个ThreadLocal对象,那么使用Thread做key也是可以的,代表每个Thread线程对应一个value。

public class ThreadLocalService {
   private static final ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
}   

但是,在实际应用程序开发过程中,一个线程中很有可能不只使用了一个ThreadLocal对象。这时使用Thread做key就会产生混淆。

public class ThreadLocalService {
   private static final ThreadLocal<Integer> threadLocal1 = new ThreadLocal<>();
   private static final ThreadLocal<Integer> threadLocal2 = new ThreadLocal<>();
   private static final ThreadLocal<Integer> threadLocal3 = new ThreadLocal<>();
}

image-20240920114225112

所以,不能使用Thread做key,而应该改成用ThreadLocal对象做key,这样才能通过具体ThreadLocal对象的get()方法,获取到当前线程的ThreadLocalMap,然后进一步获取到对应的Entry。

image-20240920114348230

5.ThreadLocalMap如何查找数据?

​ 当我们使用ThreadLocal获取当前线程中保存的Value数据时,是以ThreadLocal对象作为Key,通过访问Thread当前线程对象的内部

ThreadLocalMap集合来获取到Value。

ThreadLocalMap集合的底层数据结构使用Entry[]数组保存Key-Value键值对数据。所以,当通过ThreadLocal的get、set()、remove()等

方法,访问ThreadLocalMap时,最终都会通过一个下标,来完成对数组中的元素访问。

int i = key.threadLocalHashCode & (len-1);

​ 通过key的“hashCode值”跟"数组的长度减1" 做“&按位与”运算。其中key就是ThreadLocal对象。这种计算方式,相当于用“hashCode值”跟“数组的长度”进行“%取余”运算。

​ 假设:len=16,key.threadLocalHashCode=31

​ hash & len-1的计算结果与hash % len的计算结果一直,均为15,但是“&按位与”运算的效率更高。

6.父子线程如何共享数据?

在实际工作中,有可能需要在父子线程中共享数据的。即:在父线程中往ThreadLocal设置了值,在子线程中能够获取到。

        ThreadLocal<String> threadLocal = new ThreadLocal<String>();
        threadLocal.set("天王盖地虎");
        System.out.println("main主线程:" + threadLocal.get());
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("子线程:" + threadLocal.get());
            }
        });
        thread.start();
    }

​ 在这种情况下使用ThreadLocal是行不通的。main方法是在主线程中执行的,相当于父线程。在main方法中开启了另外一个线程,相当于子线程。两个线程对象,各自拥有不同的ThreadLocalMap。应该使用InheritableThreadLocal,它是JDK自带的类,继承了ThreadLocal类。

        InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<String>();
        threadLocal.set("天王盖地虎");
        System.out.println("main主线程:" + threadLocal.get());
        Thread thread = new Thread(() -> {
            System.out.println("子线程:" + threadLocal.get());
        });
        thread.start();
    }
7.ThreadLocal如何避免内存泄露?

在finally调用ThreadLocal对象的remove()方法。

​ 需要特别注意的地方是:一定要在finally代码块中,调用remove()方法清理没用的数据。如果业务代码出现异常,也能及时清理没用的数据。

​ remove()方法中会把Entry中的key和value都设置成null,这样就能被GC及时回收,无需触发额外的清理机制,所以它能解决内存泄露问题。

8.ThreadLocal应用场景

1线程数据隔离

​ ThreadLocal的主要价值在于线程隔离,ThreadLocal中的数据只属于当前线程,该数据对别的线程是不可见的,起到隔离作用。这样操作,可以在多线程环境下,可以防止当前线程的数据被其他线程修改。另外,由于各个线程之间的数据相互隔离,避免了同步加锁带来的性能损失,大大提升了并发性的性能。

​ 例如:SqlSession会话对象绑定,避免多个线程使用同一个SqlSession对象,由于关闭导致异常。

    private static final ThreadLocal threadSession = new ThreadLocal();
    public static SqlSession getSession() {
        SqlSession s = (SqlSession) threadSession.get();
        if (s == null) {
            s = getSqlSessionFactory().openSqlSession();
            threadSession.set(s);
        }
    }

2跨函数传递

​ 数据通常用于同一个线程内,跨类、跨方法传递数据时,如果不用ThreadLocal,那么相互之间的数据传递势必要靠返回值和参数,这样无形之中增加了这些类或者方法之间的耦合度.

 // SpringMVC或SpringBoot框架使用中获取HttpServletRequest
    HttpServletRequest request =
            ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
   @Nullable
    public static RequestAttributes getRequestAttributes() {
        // 获取当前线程中的存储的Request Attribute
        RequestAttributes attributes = requestAttributesHolder.get();
        if (attributes == null) {
            attributes = inheritableRequestAttributesHolder.get();
        }
        return attributes;
    }
    private static final ThreadLocal<RequestAttributes> requestAttributesHolder =  new NamedThreadLocal<>("Request attributes");
    private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =  new NamedInheritableThreadLocal<>("Request context");

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

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

相关文章

STM32CubeIDE | 使用HAL库的ADC读取内部传感器温度

1、cubemx配置 1.1、系统配置 1.2、GPIO配置 PB2设置为“GPIO_Output” user label设置为“LED” 1.3、串口配置 模式选择为“Asynchronous”&#xff0c;其他默认 1.4、时钟树配置 全部保持默认 2、ADC配置 通道选择“Temperature Sensor Channel”&#xff0c;其他默认 …

基于STM32的Zeta型数控电源设计

本设计基于STM32F103C6T6为主控芯片&#xff0c;基于Zeta型DC/DC电源的拓扑结构设计一种数控电源。系统包含单片机主控模块、Zeta型升降压模块、驱动模块、电流采样模块、电压采样模块、OLED显示模块、电源模块及按键模块。用电流采样模块采集电流&#xff0c;电压采样模块采集…

Skyvern:基于LLM和CV的开源RPA

Skyvern 使用 LLM 和计算机视觉来自动化基于浏览器的工作流程。它提供了一个简单的 API 端点&#xff0c;可以完全自动化大量网站上的手动工作流程&#xff0c;从而取代脆弱或不可靠的自动化解决方案。 传统的浏览器自动化方法需要为网站编写自定义脚本&#xff0c;通常依赖于…

基于小安派AiPi-Eyes-Rx的N合1触摸屏游戏

基于小安派AiPi-Eyes-Rx的N合1触摸屏游戏 目前存在的游戏&#xff1a; 植物大战僵尸&#xff1a;demos/pvz羊了个羊&#xff1a;demos/yang消消乐&#xff1a;demos/xiaoxiaole华容道&#xff1a;demos/huarongdao PVZ功能展示可见&#xff1a; 羊了个羊&#xff1a; 消消…

在多态的方法调用中为什么会出现“左边编译左边运行”的现象?多态创建的对象到底是谁属于父类还是子类?通过深扒集合remove方法调用理解其原理

目录 “左边编译左边运行”的两个原因&#xff1a; 什么是“编译看左边&#xff0c;运行看右边”&#xff1f; 为什么会出现“左边编译左边运行”现象&#xff1f; 1. 子类没有重写父类的方法 2. 重载与重写的混淆&#xff08;重难点&#xff09; 问题&#xff1a;编译器是…

JAVA开源项目 体育馆管理系统 计算机毕业设计

本文项目编号 T 048 &#xff0c;文末自助获取源码 \color{red}{T048&#xff0c;文末自助获取源码} T048&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析5.4 用例设计 六、核…

每日论文1——应用于65nm CMOS锁相环完全电流匹配的电荷泵

《A Charge Pump with Perfect Current Matching Applied to Phase-Locked Loop in 65nm CMOS》2021 IEEE 14th International Conference on ASIC 电荷泵PLL的结构框图如图&#xff0c;其中CP的充放电电流不匹配会引起PLL的频率误差和杂散。 传统的电荷泵结构在输出处的电平…

强烈推荐的10款企业文件加密软件|2024企业办公文件加密

随着信息安全威胁的不断增加&#xff0c;企业文件加密成为保护敏感数据的重要手段。在2024年&#xff0c;有多款文件加密软件可供选择&#xff0c;帮助企业提高数据安全性。以下是十款强烈推荐的企业文件加密软件。 1.安秉加密软件 安秉加密软件专为企业设计&#xff0c;主要用…

【Web】初识Web和Tomcat服务器

目录 前言 一、认识web 1. 软件架构模式 2. web资源 3. URL请求路径&#xff08;统一资源定位符&#xff09; 二、Tomcat服务器 1. 简介 2. tomcat服务器的目录结构 3.使用tomcat服务器启动失败的常见原因 3.1 端口冲突 3.2 jdk环境变量配置出错 三、使用Tomcat发布…

OpenSSH9.7升级至OpenSSH9.9(openssh7以后所有版本升级均可使用该方法)

1、查看当前openssh版本&#xff0c;使用命令ssh -V 2、开启当前主机的telnet和ftp service xinetd start #开启telnetservice vsftpd start #开启ftp 3、下载openssh最新版 下载地址: OPENSSH下载地址https://cdn.openbsd.org/pub/OpenBSD/OpenSSH/portable/ 4、开始安…

【Redis入门到精通五】Java如何像使用MySQL一样使用Redis(jedis安装及使用)

目录 Jedis 1.jedis是什么 2.jedis的安装配置 3.jedis的基础命令操作展示 1.set和get操作&#xff1a; 2.exists和del操作&#xff1a; 3.keys和type操作&#xff1a; 4. expire和ttl&#xff1a; Jedis Java 操作 redis 的客⼾端有很多&#xff0c;其中最知名的是 jedi…

大模型备案和互联网算法备案的区别?

最近&#xff0c;接到很多客户的电话咨询大模型备案和互联网算法备案&#xff0c;好多人搞不清楚这两个有什么关系&#xff1f;有什么区别&#xff1f;我们先来看下全国大模型备案和互联网算法备案通过的情况是怎么样的&#xff1f; 截至写稿之时&#xff0c;全国通过大模型备…

2024最新最全【Qubes Linux系统安装下载】零基础入门到精通!

Qubes Linux的安装和设置过程非常简单。它也是一个以安全为中心的桌面操作系统&#xff0c;旨在通过隔离提供安全性&#xff0c;这对于系统管理员、记者和有道德的黑客来说是一个很好的卖点。 Qubes是一个使用Xen的 Linux 发行版&#xff0c;这是一种虚拟化技术&#xff0c;它…

卫星图片地面目标识别检测数据集 1500张 yolo数据集 已增强

卫星图像地面目标识别数据集&#xff08;Satellite Image Ground Target Recognition Dataset, SIGTRD&#xff09; 摘要 SIGTRD 是一个专门为卫星图像中的地面目标识别而设计的数据集&#xff0c;它包含了一系列常见的基础设施和交通工具类型。该数据集提供了1500张卫星图像…

基于单片机的指纹打卡系统

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 基于STC89C52RC&#xff0c;采用两个按键替代指纹&#xff0c;一个按键按下&#xff0c;LCD12864显示比对成功&#xff0c;则 采用ULN2003驱动步进电机转动&#xff0c;表示开门&#xff0c;另一个…

电脑桌面归纳小窗口如何设置?电脑桌面一键整理工具分享!

电脑桌面归纳小窗口如何设置&#xff1f;日常使用电脑的过程中&#xff0c;随着文件、应用程序的不断增加&#xff0c;桌面往往会变得杂乱无章&#xff0c;这不仅影响了美观&#xff0c;也降低了工作效率。幸运的是&#xff0c;现代技术为我们提供了多种桌面整理工具&#xff0…

【QA-MISRA】解决使用命令行扫描项目后看不到报告的问题

1、 文档目标 解决使用命令行扫描项目后看不到报告的问题 2、 问题场景 客户使用命令行扫描项目后看不到报告&#xff0c;原因是客户未设置和勾选报告格式就导出了DAX文件进行命令行直接扫描。 3、软硬件环境 1、软件版本&#xff1a; QA-MISRA23.04 2、机器环境&#xff1…

李宏毅2023机器学习作业HW07解析和代码分享

ML2023Spring - HW7 相关信息&#xff1a; 课程主页 课程视频 Kaggle link 回来了 : ) Sample code HW07 视频 HW07 PDF 个人完整代码分享: GitHub | Gitee | GitCode P.S. HW7 的代码都很易懂&#xff0c;可以和 2024 年的新课&#xff1a;生成式AI导论做一个很好的衔接&#…

开源 AI 智能名片与 S2B2C 商城小程序:嫁接权威实现信任与增长

摘要&#xff1a;本文探讨了嫁接权威在产品营销中的重要性&#xff0c;并结合开源 AI 智能名片与 S2B2C 商城小程序&#xff0c;阐述了如何通过与权威关联来建立客户信任&#xff0c;提升产品竞争力。强调了在当今商业环境中&#xff0c;巧妙运用嫁接权威的方法&#xff0c;能够…

一款前后端分离设计的企业级快速开发平台,支持单体服务与微服务之间灵活切换(附源码)

前言 当前软件开发面临诸多挑战&#xff0c;诸如开发效率低下、重复工作多、维护成-本高等问题&#xff0c;这些问题在一定程度上阻碍了项目的进展。针对这些痛点&#xff0c;我们迫切需要一款既能提升开发效率又能降低维护成-本的处理方案。由此&#xff0c;一款基于前后端分…