ThreadLocal的使用介绍和底层原理解析和开源框架的使用实例

文章目录

  • ThreadLocal的使用介绍和底层原理解析和开源框架的使用实例
    • ThreadLocal简介
    • ThreadLocal使用示例
    • ThreadLocal原理解析
    • Spring中ThreadLocal的应用
    • 小结
    • ThreadLocal的使用步骤
    • 常见面试题
    • 案例解析(框架源码经典案例)
    • 案例实战

ThreadLocal的使用介绍和底层原理解析和开源框架的使用实例

ThreadLocal简介

ThreadLocal是一个线程内部的数据存储类,它可以为每个线程提供独立的变量副本,不同线程间的变量无法相互访问和修改。这避免了每个线程都要维护一套独立变量的麻烦,并且也减少了线程之间不必要的数据争用。ThreadLocal适用于这样的场景:每个线程需要有自己单独的实例,而不是共享实例。例如,在 web 应用中,每个请求被一个新的线程处理,每个线程需要有自己的变量实例。

ThreadLocal使用示例

public class ThreadLocalExample {
  // 线程局部变量,每个线程有自己的变量副本
  private ThreadLocal<String> threadLocal = new ThreadLocal<>();
  
  public void set(String value) {
    threadLocal.set(value);
  }
  
  public String get() {
    return threadLocal.get();
  }
}

public class ThreadLocalTest {
  public static void main(String[] args) {
    ThreadLocalExample example = new ThreadLocalExample();
    
    // 线程1设置threadLocal变量
    example.set("Thread1 local variable"); 
    System.out.println("Thread1 get: " + example.get());
    
    // 线程2无法获取线程1设置的threadLocal变量
    Thread thread2 = new Thread() {
      public void run() {
        example.set("Thread2 local variable"); 
        System.out.println("Thread2 get: " + example.get());
      }
    };
    thread2.start();
  }
}

运行结果:
Thread1 get: Thread1 local variable
Thread2 get: Thread2 local variable每个线程获取自己设置的值,并不同线程间互不干扰。

ThreadLocal原理解析

ThreadLocal内部使用ThreadLocalMap来存储每个线程的变量副本。ThreadLocalMap是ThreadLocal的静态内部类,每个线程都有自己的ThreadLocalMap副本。

ThreadLocal中get()方法的实现如下:

public T get() {
  Thread t = Thread.currentThread();
  ThreadLocalMap map = getMap(t);
  if (map != null) {
    ThreadLocalMap.Entry e = map.getEntry(this);
    if (e != null) {
      @SuppressWarnings("unchecked")
      T result = (T)e.value;
      return result;
    }
  }
  return setInitialValue(); 
}
  1. 获取当前线程对象
  2. 获取当前线程的ThreadLocalMap(实际上是从当前线程的ThreadLocalMap变量中获取)
  3. 在ThreadLocalMap中获取当前ThreadLocal变量对应的value值
  4. 如果不存在,调用setInitialValue()方法初始化value值,并存储到ThreadLocalMap中

这样,每个线程的ThreadLocal变量都被存储在自己的ThreadLocalMap中,相互独立,互不干扰。

ThreadLocalMap使用ThreadLocal对象作为key来存储value值。当ThreadLocal对象被回收时,由弱引用产生的key会在下一次GC时被清除,这会导致value值无法被访问到,出现内存泄漏,所以我们应该手动调用remove()方法,在ThreadLocal不再使用时清除它。

Spring中ThreadLocal的应用

Spring框架中大量使用了ThreadLocal,例如:

  • TransactionSynchronizationManager: 管理线程事务上下文信息。
  • RequestContextHolder: 存储request上下文,用于获取request信息。
  • LocaleContextHolder: 存储locale上下文,用于获取locale信息。

这些类都使用ThreadLocal来为每个线程提供单独变量副本,避免了线程间数据交叉和覆盖的问题。

@Component
public class RequestHolder {
  private ThreadLocal<HttpServletRequest> requestHolder = new ThreadLocal<>();
  
  public void setRequest(HttpServletRequest request) {
    requestHolder.set(request);
  }
  
  public HttpServletRequest getRequest() {
    return requestHolder.get(); 
  }
}

这样每个线程在处理request时可以调用setRequest()方法存储自己的request对象,在其他地方需要获取request信息时,调用getRequest()方法即可获取当前线程对应的request对象。这就避免了每个线程都要维护一个request对象的麻烦,也减少了线程之间request对象混淆的问题。

小结

ThreadLocal为每个线程提供独立的变量副本,实现了线程隔离。它的主要作用是为每个线程保存一些 thread-local 的上下文信息,这些信息在线程的生命周期内起作用。

它的内部原理是使用ThreadLocalMap来存储每个线程对应的变量副本,键值为ThreadLocal对象,值则为变量副本。

它应用在许多地方,如Spring框架等,用于避免线程间数据交叉和覆盖的问题。

但是它也有一定的弊端,由于ThreadLocalMap使用ThreadLocal作为key,如果ThreadLocal被回收,就可能出现内存泄漏的问题。所以应该手动调用ThreadLocal的remove()方法,在ThreadLocal不再使用时清除它。

ThreadLocal的使用步骤

  1. 定义ThreadLocal变量:
private ThreadLocal<String> threadLocal = new ThreadLocal<>();
  1. 在每个线程内设置ThreadLocal变量:
threadLocal.set("value");
  1. 获取ThreadLocal变量:
String value = threadLocal.get();
  1. 删除ThreadLocal变量:
threadLocal.remove();
  1. 目前ThreadLocal类提供的方法有:
  • set(T value): 设置当前线程的thread local变量的值。
  • get(): 获取当前线程的thread local变量的值。
  • remove(): 删除当前线程的thread local变量的值。
  • initialValue(): 返回当前线程第一次调用get()时的值,后续调用get()会直接返回这个值。
  1. ThreadLocal应用举例:
  • 解决数据库连接共享问题:每个线程都有自己的数据库连接,避免线程之间的连接混用。
private ThreadLocal<Connection> connectionHolder = new ThreadLocal<>();

public Connection getConnection() {
  Connection conn = connectionHolder.get();
  if (conn == null) {
    conn = DriverManager.getConnection("jdbc:mysql://localhost/test", "root", "root");
    connectionHolder.set(conn);
  }
  return conn;
}  
  • 解决Session共享问题:每个线程都有自己的Session实例。
private ThreadLocal<HttpSession> sessionHolder = new ThreadLocal<>();

public HttpSession getSession() {
  HttpSession session = sessionHolder.get();
  if (session == null) {
    session = request.getSession(); 
    sessionHolder.set(session);
  }
  return session;
}
  1. ThreadLocal内存泄漏问题的解决:

由于ThreadLocalMap使用ThreadLocal作为key来存储entry,如果ThreadLocal被回收,key变成null,就会出现内存泄漏。所以ThreadLocal使用完毕后,需要调用remove()方法清除数据,避免出现内存泄漏。

threadLocal.remove();

常见面试题

  1. ThreadLocal能否解决线程安全问题?

答:ThreadLocal不能解决线程安全问题。ThreadLocal为每个线程提供独立的变量副本,实现线程隔离,但并不保证线程安全。如果多个线程同时修改同一个ThreadLocal变量,还是需要额外的同步措施保证线程安全。

  1. ThreadLocal会引起内存泄漏么?如何避免?

答:ThreadLocal会引起内存泄漏。因为ThreadLocalMap使用ThreadLocal作为key来存储entry,如果ThreadLocal被回收,key变成null,就会出现内存泄漏。
解决方法是在ThreadLocal不再使用时,手动调用remove()方法清除数据,避免出现内存泄漏。

  1. ThreadLocal的value为什么推荐使用引用类型?

答:因为每个线程访问自己的副本变量,如果使用基本类型,ThreadLocal需要为每个线程创建一个变量副本,这会消耗较多内存。
而如果使用引用类型,每个线程访问的都是同一个引用对象的副本,只是每个线程可以对这个对象进行修改,这可以节省内存,所以推荐ThreadLocal的value使用引用类型。

  1. ThreadLocalMap的工作原理是什么?

答:ThreadLocalMap是ThreadLocal的静态内部类,每个线程都有自己的ThreadLocalMap副本。
ThreadLocalMap使用ThreadLocal对象作为key来存储value值。在调用ThreadLocal的get()方法时,会先得到当前线程的ThreadLocalMap,然后再从其中获取与当前ThreadLocal对象关联的值。
put方法会将ThreadLocal对象作为key放入map中,并关联一个value。
当ThreadLocal对象被回收时,由弱引用产生的key会在下一次GC时被清除,这会导致value值无法被访问到,出现内存泄漏,所以在ThreadLocal不再使用时需要手动调用remove()方法清除数据。

  1. 个人理解ThreadLocal的主要作用和应用场景?

答:ThreadLocal的主要作用是为每个线程提供独立的变量副本,实现线程隔离。
它的应用场景主要有:

  • 为每个线程绑定请求相关数据,避免同一个请求被不同线程处理时出现数据混淆的问题。
  • 为每个线程单独绑定数据库连接、Session等资源,避免线程间共享资源。
  • 解决变量共享导致的线程安全问题,通过给每个线程独立变量副本来隔离线程。

案例解析(框架源码经典案例)

这里我们以Spring中的ThreadLocal应用举个例子加深理解。

Spring中TransactionSynchronizationManager使用ThreadLocal来管理事务上下文信息。它定义了两个ThreadLocal变量:

private static final ThreadLocal<Map<Object, Object>> resources = 
    new NamedThreadLocal<Map<Object, Object>>("Transactional resources");
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = 
    new NamedThreadLocal<Set<TransactionSynchronization>>("Transaction synchronizations");
  • resources: 用于存储事务相关资源,如数据库连接、Session等。
  • synchronizations: 用于存储事务同步对象,如事务完成后需要执行的回调等。

当开始一个事务时,通过TransactionSynchronizationManager进行事务上下文的存储:

StaticTransactionSynchronizationAdapter.registerSynchronization(
    new TransactionSynchronizationAdapter() {
        @Override
        public void afterCompletion(int status) {
            if (status == STATUS_COMMITTED) {
                afterCommit();
            }
            else {
                afterRollback(); 
            }
        }
    }
);
TransactionSynchronizationManager.bindResource(ds, conn);
  • 向synchronizations ThreadLocal集合中添加一个事务同步对象,用于在事务完成后执行回调。
  • 向resources ThreadLocal集合中添加连接资源,以方便事务管理器管理。

事务完成时,TransactionSynchronizationManager会执行:

TransactionSynchronizationManager.initSynchronization();
try {
    // 执行回调
    TransactionSynchronizationManager.cleanupSynchronization();
}
finally {
    // 清除ThreadLocal上的变量
    TransactionSynchronizationManager.clear();        
}
  • 调用initSynchronization执行注册的同步回调。
  • 调用cleanupSynchronization执行后续清理工作。
  • 调用clear()方法清除ThreadLocal上的事务上下文,避免内存泄漏。

这样,通过ThreadLocal为每个事务线程独立存储事务上下文,避免了线程间数据混淆和干扰的问题。同时也在事务完成后手动调用clear()方法清除ThreadLocal,解决了内存泄漏的问题。

这就是ThreadLocal在Spring事务管理中的典型应用, hope这能加深您对ThreadLocal用法的理解。

案例实战

这里我们来实现一个简单的Session管理,使用ThreadLocal为每个线程单独绑定Session实例。

public class SessionManager {
  private ThreadLocal<HttpSession> sessionHolder = new ThreadLocal<>();
  
  public HttpSession getSession() {
    HttpSession session = sessionHolder.get();
    if (session == null) {
      session = request.getSession(); 
      sessionHolder.set(session);
    }
    return session;
  }
  
  public void clear() {
    sessionHolder.remove();
  }
}
  • 定义ThreadLocal变量sessionHolder来存储每个线程的HttpSession实例。
  • getSession()方法先从sessionHolder中获取 SESSION,如果不存在则创建一个新的SESSION,并存储到sessionHolder中。
  • clear()方法用于手动清除sessionHolder,避免内存泄漏。

使用方式:

// 获取Session
SessionManager manager = new SessionManager();
HttpSession session = manager.getSession();

// 使用Session
session.setAttribute("key", "value");

// 获取Attribute
String value = (String) session.getAttribute("key");

// 手动清除
manager.clear(); 

使用ThreadLocal为每个线程单独存储SESSION,避免了线程间SESSION实例的混淆,也能很好地管理SESSION生命周期。
同时也演示了如何防止ThreadLocal内存泄漏的问题,手动调用clear()方法清除ThreadLocal变量。此案例结合理论介绍了ThreadLocal的整个使用过程,包括定义ThreadLocal变量,为每个线程单独设置变量值,获取变量值,清除ThreadLocal变量等步骤。并分析了其工作原理和应用场景,希望能够帮助大家进一步理解和熟练掌握ThreadLocal。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java——线程池详细讲解

文章目录 一、线程池一、线程池基础1.1 什么是线程池1.2 为什么使用线程池1.3 线程池有哪些优势1.4 应用场景 二、线程池使用2.1 Java内置线程池 ThreadPoolExecutor2.1.1 线程池的七个参数2.1.1.1 **int corePoolSize 核心线程数量**2.1.1.2 int maximumPoolSize 最大线程数2.…

[OtterCTF 2018]之Misc篇(NSSCTF)刷题记录⑦

NSSCTF-Misc篇-[OtterCTF 2018] [OtterCTF 2018]General Info[OtterCTF 2018]Play Time[OtterCTF 2018]Silly Rick[OtterCTF 2018]What the password?[OtterCTF 2018]Name Game[OtterCTF 2018]Hide And Seek[OtterCTF 2018]Name Game 2[OtterCTF 2018]Path To Glory[OtterCTF …

2023年第二十届五一数学建模竞赛C题:“双碳”目标下低碳建筑研究-思路详解与代码答案

该题对于模型的考察难度较低&#xff0c;难度在于数据的搜集以及选取与处理。 这里推荐数据查询的网站&#xff1a;中国碳核算数据库&#xff08;CEADs&#xff09; https://www.ceads.net.cn/ 国家数据 国家数据​data.stats.gov.cn/easyquery.htm?cnC01 以及各省市《统…

安陆EGS20 SDRAM仿真

目录 一. 搭建仿真平台 二. 实现SDRAM连续写入1024个数据&#xff0c;然后再连续读出&#xff0c;并比较 1. 调试过程中问题&#xff1a; 2. 顶层代码 3. 功能代码 三. SDRAMFIFO实现上述功能调试 1. 代码设计要点 2. 仿真过程问题 3. 上板运行调试 安陆反馈&#xf…

YOLOv6 4.0 使用记录: OpenCV DNN C++推理

目录 1、下载源码 2、下载权重文件 3、配置环境 4、推理 6、ONNX格式导出 权重文件为yolov6list_s.pt 权重为yolov6.pt 7、opencv DNN推理 8、个人总结 1、下载源码 下载最新的4.0版本的 2、下载权重文件 我下的是YOLOv6Lite-S 3、配置环境 cd到项目目录&#xff0c;运…

3.6 cache存储器

学习步骤&#xff1a; 我会采取以下几个步骤来学习Cache存储器&#xff1a; 确定学习目标&#xff1a;Cache存储器作为一种高速缓存存储器&#xff0c;通常用于提高计算机系统的运行效率。因此&#xff0c;我需要明确学习Cache存储器的目的&#xff0c;包括了解其原理、结构和…

一图看懂 requests 模块:用Python编写、供人类使用的HTTP库, 资料整理+笔记(大全)

本文由 大侠(AhcaoZhu)原创&#xff0c;转载请声明。 链接: https://blog.csdn.net/Ahcao2008 一图看懂 requests 模块&#xff1a;用Python编写、供人类使用的HTTP库, 资料整理笔记&#xff08;大全&#xff09; 摘要模块图类关系图模块全展开【requests】统计常量str 模块3 w…

java数据结构之HashMap

目录 前言 1、初始化 1.1、初始化 1.2、插入第一条数据 2、数组 链表 2.1、插入数据&#xff1a;没有hash冲突 2.2、插入数据&#xff1a;Key不同&#xff0c;但产生hash冲突 2.3、插入数据&#xff1a;Key相同 3、数组 红黑树 3.1、链表如何转化为红黑树&#xff1f; 3.…

golang - switch

switch 的使用 switch 语句用于基于不同条件执行不同操作&#xff0c;&#xff0c;直每一个 case 分支都是唯一的&#xff0c;从上到下逐一测试到匹配为止匹配项后面也不需要再加 break switch 表达式 {case 表达式1, 表达式2, ... :语句块1case 表达式2, 表达式3, ... :语句块…

GPT:你知道这五年我怎么过的么?

时间轴 GPT 首先最初版的GPT&#xff0c;来源于论文Improving Language Understanding by Generative Pre-Training&#xff08;翻译过来就是&#xff1a;使用通用的预训练来提升语言的理解能力&#xff09;。GPT这个名字其实并没有在论文中提到过&#xff0c;后人将论文名最后…

【Unity3D小功能】Unity3D中实现轮船在水面上移动效果

推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享简书地址我的个人博客 大家好&#xff0c;我是佛系工程师☆恬静的小魔龙☆&#xff0c;不定时更新Unity开发技巧&#xff0c;觉得有用记得一键三连哦。 一、前言 标题是啥我写啥&#xff0c;大家好&#xff0c;今天给大家带来…

你的 Kubernetes 安全吗?最新benchmark的重要趋势解读

导语 疫情过后经济处在缓慢复苏的阶段&#xff0c;对于企业应该优先考虑数字化转型&#xff0c;因为它可以促进增长和创新。 不可避免地&#xff0c;当今的数字化转型计划依赖于云的可扩展性和灵活性。 虽然在云中启动应用程序和服务带来了许多机遇&#xff0c;但也带来了新的…

云原生Istio架构和组件介绍

目录 1 Istio 架构2 Istio组件介绍2.1 Pilot2.2 Mixer2.3 Citadel2.4 Galley2.5 Sidecar-injector2.6 Proxy(Envoy)2.7 Ingressgateway2.8 其他组件 1 Istio 架构 Istio的架构&#xff0c;分为控制平面和数据面平两部分。 - 数据平面&#xff1a;由一组智能代理&#xff08;[En…

HCIA-RS实验-路由配置-静态路由缺省路由(2)

接上文HCIA-RS实验-路由配置-静态路由&缺省路由 继续完成缺省路由&#xff1b;其他原截图就不再一一截图&#xff0c;有需要往回看一篇。 关闭上一篇的接口shutdown&#xff08;重新启动&#xff09; 上一篇在R2关闭的接口2 需要重新启动&#xff0c;输入 undo shutdown…

4月VR大数据:PICO平台应用近400款,领跑国内VR生态

Hello大家好&#xff0c;每月一期的VR内容/硬件大数据统计又和大家见面了。 想了解VR软硬件行情么&#xff1f;关注这里就对了。我们会统计Steam平台的用户及内容等数据&#xff0c;每月初准时为你推送&#xff0c;不要错过喔&#xff01; 本数据报告包含&#xff1a;Steam VR硬…

我们公司的面试,有点不一样!

我们公司的面试&#xff0c;有点不一样&#xff01; 朋友们周末愉快&#xff0c;我是鱼皮。因为我很屑&#xff0c;所以大家也可以叫我屑老板。 自从我发了自己创业的文章和视频后&#xff0c;收到了很多小伙伴们的祝福&#xff0c;真心非常感谢&#xff01; 不得不说&#…

Elasticsearch:人类语言到 Elasticsearch 查询 DSL

Elasticsearch 为开发者提供了强大的搜索功能。Elasticsearch 使用 DSL 来进行查询。对于很多从关系数据库过来的人&#xff0c;这个很显然不很适应。虽然我们可以使用 SQL 来进行查询&#xff0c;但是我们必须通过一些命令来进行转换。我们可以通过阅读文章&#xff1a; Elast…

【Java面试八股文】数据库篇

导航&#xff1a; 【黑马Java笔记踩坑汇总】JavaSEJavaWebSSMSpringBoot瑞吉外卖SpringCloud黑马旅游谷粒商城学成在线MySQL高级篇设计模式牛客面试题 目录 请你说说MySQL索引,以及它们的好处和坏处 请你说说MySQL的索引是什么结构,为什么不用哈希表 请你说说数据库索引的底…

Segmentation of retinal vessels based on MRANet

随手把一篇论文的创新部分抽取出来 MLF 为了更好地聚合每一层的上采样特征信息和MSR块的信息&#xff0c;在解码路径中使用了MLF块&#xff0c;这允许最大限度地重用功能&#xff0c;从而减少细节的损失。MLF块的结构如图2所示。 如图2所示&#xff0c;有两种输入:input1和inp…

观察 | 卫浴产业数字化转型下的中国智造样本

文 | 智能相对论 作者 | 佘凯文 数字技术的发展已成为全球科技变革向高端技术不断升级的方向。 年初&#xff0c;中共中央、国务院印发《数字中国建设整体布局规划》&#xff0c;这是党的二十大后党中央在我国数字化发展领域作出的最全面擘画&#xff0c;从顶层设计的高度对…