ThreadLocal

ThreadLocal

参考:https://blog.csdn.net/u010445301/article/details/111322569

ThreadLocal简介

  • 作用:实现线程范围内的局部变量,即ThreadLocal在一个线程中是共享的,在不同线程之间是隔离的。

  • 原理:ThreadLocal存入值时使用当前ThreadLocal实例作为key,存入当前线程对象中的Map中去。

  • 一个ThreadLocal在一个线程中是共享的,在不同线程之间又是隔离的(每个线程都只能看到自己线程的值)

    • 线程局部变量,同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本。ThreadLocal是当前线程独有的变量。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
      • 每个 Thread 内有自己的实例副本,且该副本只能由当前 Thread 使用。
      • 既然每个 Thread 有自己的实例副本,且其它 Thread 不可访问,那就不存在多线程间共享的问题。

ThreadLocal结构

在这里插入图片描述

  • 在JDK早期:每个ThreadLocal都有一个map对象,将线程作为map对象的key,要存储的变量作为map的value。
  • JDK8之后,每个Thread维护一个ThreadLocalMap对象,这个Map的key是ThreadLocal实例本身,value是存储的值要隔离的变量,是泛型,其具体过程如下:
    • 每个Thread线程内部都有一个Map(ThreadLocalMap::threadlocals);
    • Map里面存储ThreadLocal对象(key)和线程的变量副本(value);
    • Thread内部的Map由ThreadLocal维护,由ThreadLocal负责向map获取和设置变量值;
    • 对于不同的线程,每次获取副本值时,别的线程不能获取当前线程的副本值,就形成了数据之间的隔离。

JDK8之后设计的好处在于:

  • 每个Map存储的Entry的数量变少,在实际开发过程中,ThreadLocal的数量往往要少于Thread的数量,Entry的数量减少就可以减少哈希冲突。
  • 当Thread销毁的时候,ThreadLocalMap也会随之销毁,减少内存使用,早期的ThreadLocal并不会自动销毁。

结构:

在这里插入图片描述

Threadlocal是作为当前线程中属性ThreadLocalMap集合中的某一个Entry的key值Entry(threadlocal,value),虽然不同的线程之间threadlocal这个key值是一样,但是不同的线程所拥有的ThreadLocalMap是独一无二的,也就是不同的线程间同一个ThreadLocal(key)对应存储的值(value)不一样,从而到达了线程间变量隔离的目的,但是在同一个线程中这个value变量地址是一样的。

每个Thread对象都有一个ThreadLocalMap,当创建一个ThreadLocal的时候,就会将该ThreadLocal对象添加到该Map中,其中键就是ThreadLocal,值可以是任意类型。ThreadLocalMap是ThreadLocal的内部静态类,它的构成主要是用Entry来保存数据 ,而且还是继承的弱引用。在Entry内部使用ThreadLocal作为key,使用我们设置的value作为value。

想要存入的ThreadLocal中的数据实际上并没有存到ThreadLocal对象中去,而是以这个ThreadLocal实例作为key存到了当前线程中的一个Map中去了,获取ThreadLocal的值时同样也是这个道理。这也就是为什么ThreadLocal可以实现线程之间隔离的原因了

ThreadLocal源码

示例

public class ThreadLocalTest {private  static ThreadLocal<String> localVar = new ThreadLocal<>();static void print(String str){System.out.println(str + ":" + localVar.get());localVar.remove();}public static void main(String[] args) throws InterruptedException {new Thread(new Runnable() {@Overridepublic void run() {ThreadLocalTest.localVar.set("local_A");print("A");System.out.println("after remove:" + localVar.get());}},"A").start();Thread.sleep(1000);new Thread(new Runnable() {@Overridepublic void run() {ThreadLocalTest.localVar.set("local_B");print("B");System.out.println("after remove:" + localVar.get());}}, "B").start();}
}

ThreadLocal的set()方法

  • 获取当前线程
  • 获取线程的属性threadLocalMap不为空,则直接更新要保存的变量值,否则创建threadLocalMap,并赋值

ThreadLocal的get方法

  • 获取当前线程
  • 获取当前线程的threadLocalMap
  • 如果map数据不为空,获取threadLocalMap中存储的值
  • 如果数据为空,则初始化,初始化的结果,threadLocalMap中存放key值为threadLocal,值为null

ThreadLocal的remove方法

  • remove方法,直接将ThrealLocal 对应的值从当前相差Thread中的ThreadLocalMap中删除。为什么要删除,这涉及到内存泄露的问题。实际上 ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用,弱引用的特点是,如果这个对象只存在弱引用,那么在下一次垃圾回收的时候必然会被清理掉。

使用场景

ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景

  • 每个线程需要有自己单独的实例
  • 实例需要在多个方法中共享,但不希望被多线程共享

场景一:存储用户session

private static final ThreadLocal threadSession = new ThreadLocal();public static Session getSession() throws InfrastructureException {Session s = (Session) threadSession.get();try {if (s == null) {s = getSessionFactory().openSession();threadSession.set(s);}} catch (HibernateException ex) {throw new InfrastructureException(ex);}return s;
}

场景二、数据库连接,处理数据库事务

场景三、数据跨层传递(controller,service, dao)

每个线程内需要保存类似于全局变量的信息(例如在拦截器中获取的用户信息),可以让不同方法直接使用,避免参数传递的麻烦却不想被多线程共享(因为不同线程获取到的用户信息不一样)。

例如,用 ThreadLocal 保存一些业务内容(用户权限信息、从用户系统获取到的用户名、用户ID 等),这些信息在同一个线程内相同,但是不同的线程使用的业务内容是不相同的。

在线程生命周期内,都通过这个静态 ThreadLocal 实例的 get() 方法取得自己 set 过的那个对象,避免了将这个对象(如 user 对象)作为参数传递的麻烦。

比如一个用户系统,当一个请求进来的时候,一个线程会负责执行这个请求,然后这个请求就会依次调用service-1()、service-2()、service-3()、service-4(),这4个方法可能是分布在不同的类中的。

package com.kong.threadlocal;public class ThreadLocalDemo05 {public static void main(String[] args) {User user = new User("jack");new Service1().service1(user);}}class Service1 {public void service1(User user){//给ThreadLocal赋值,后续的服务直接通过ThreadLocal获取就行了。UserContextHolder.holder.set(user);new Service2().service2();}
}class Service2 {public void service2(){User user = UserContextHolder.holder.get();System.out.println("service2拿到的用户:"+user.name);new Service3().service3();}
}class Service3 {public void service3(){User user = UserContextHolder.holder.get();System.out.println("service3拿到的用户:"+user.name);//在整个流程执行完毕后,一定要执行removeUserContextHolder.holder.remove();}
}class UserContextHolder {//创建ThreadLocal保存User对象public static ThreadLocal<User> holder = new ThreadLocal<>();
}class User {String name;public User(String name){this.name = name;}
}执行的结果:service2拿到的用户:jack
service3拿到的用户:jack

场景四、Spring使用ThreadLocal解决线程安全问题

Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全的“状态性对象”采用ThreadLocal进行封装,让它们也成为线程安全的“状态性对象”,因此有状态的Bean就能够以singleton的方式在多线程中正常工作了。
不同的线程在使用TopicDao时,先判断connThreadLocal.get()是否为null,如果为null,则说明当前线程还没有对应的Connection对象,这时创建一个Connection对象并添加到本地线程变量中;如果不为null,则说明当前的线程已经拥有了Connection对象,直接使用就可以了。这样,就保证了不同的线程使用线程相关的Connection,而不会使用其他线程的Connection。因此,这个TopicDao就可以做到singleton共享了。

当线程使用threadlocal 时,是将threadlocal当做当前线程thread的属性ThreadLocalMap 中的一个Entry的key值,实际上存放的变量是Entry的value值,我们实际要使用的值是value值。value值为什么不存在并发问题呢,因为它只有一个线程能访问。threadlocal可以当做一个索引看待,可以有多个threadlocal 变量,不同的threadlocal对应于不同的value值,他们之间互不影响。ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。

ThreadLocal 内存泄露的原因

Entry将ThreadLocal作为Key,值作为value保存,它继承自WeakReference,是个弱引用。

主要两个原因:

  • 1 . 没有手动删除这个 Entry。。只要在使用完下 ThreadLocal ,调用其 remove 方法删除对应的 Entry ,就能避免内存泄漏。
  • 2 . CurrentThread 当前线程依然运行。。由于ThreadLocalMap 是 Thread 的一个属性,被当前线程所引用,所以ThreadLocalMap的生命周期跟 Thread 一样长。如果threadlocal变量被回收,那么当前线程的threadlocal 变量副本指向的就是key=null, 也即entry(null,value),那这个entry对应的value永远无法访问到。实际私用ThreadLocal场景都是采用线程池,而线程池中的线程都是复用的,这样就可能导致非常多的entry(null,value)出现,从而导致内存泄露。

综上, ThreadLocal 内存泄漏的根源是:
由于ThreadLocalMap 的生命周期跟 Thread 一样长,对于重复利用的线程来说,如果没有手动删除(remove()方法)对应 key 就会导致entry(null,value)的对象越来越多,从而导致内存泄漏.

正确使用

  • 将ThreadLocal变量定义成private static的,这样的话ThreadLocal的生命周期就更长,由于一直存在ThreadLocal的强引用,所以ThreadLocal也就不会被回收,也就能保证任何时候都能根据ThreadLocal的弱引用访问到Entry的value值,然后remove它,防止内存泄露
  • 每次使用完ThreadLocal,都调用它的remove()方法,清除数据。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

c高级day1(9.6) 离线软件安装,文件相关指令,文件权限相关指令,

作业: 使用cut截取出Ubuntu用户的家目录&#xff0c;要求&#xff1a;不能使用":"作为分割 不会 Xmind&#xff1a;

vue2踩坑之项目:生成二维码使用vue-print-nb打印二维码

1. vue2安装 npm install vue-print-nb --save vue3安装 npm install vue3-print-nb --save 2. //vue2 引入方式 全局 main.js import Print from vue-print-nb Vue.use(Print) ------------------------------------------------------------------------------------ //vue2 …

WEBGL(4):动态绘制点并根据详细自定义颜色

1 实现代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevice-width, …

前端面试中Vue的有经典面试题三

11. 网页从输入网址到渲染完成经历了哪些过程&#xff1f; 大致可以分为如下7步&#xff1a; 输入网址&#xff1b; 发送到DNS服务器&#xff0c;并获取域名对应的web服务器对应的ip地址&#xff1b; 与web服务器建立TCP连接&#xff1b; 浏览器向web服务器发送http请求&a…

windows 不能ping通虚拟机问题

先查看windows网卡 查看虚拟机种 对应VMnet8种的 nat &#xff08;我用的是这种连接方式&#xff09;设置 问题是不在同一个网段&#xff0c;修改windows VMnet8网卡的配置 保证网关、网段是一样的 现在ping问题解决&#xff0c;也能windows远程连接虚拟机

Bridge Champ举办人机对战赛:NFT游戏与传统竞技共生发展编织新格局

概要 现在,NFT与体育竞技正日益紧密地联系在一起。一些体育项目开始推出与赛事或球队相关的NFT,同时也有部分NFT游戏开始举办电子竞技赛事。这种共生发展正在改变体育竞技的生态。 笔者采访了桥牌冠军项目相关负责人,探讨NFT游戏与传统体育竞技的融合潜力。桥牌冠军近期成功举…

(二十一)大数据实战——Flume数据采集之复制和多路复用案例实战

前言 本节内容我们完成Flume数据采集的一个多路复用案例&#xff0c;使用三台服务器&#xff0c;一台服务器负责采集本地日志数据&#xff0c;通过使用Replicating ChannelSelector选择器&#xff0c;将采集到的数据分发到另外俩台服务器&#xff0c;一台服务器将数据存储到hd…

pytorch-神经网络-手写数字分类任务

Mnist分类任务&#xff1a; 网络基本构建与训练方法&#xff0c;常用函数解析 torch.nn.functional模块 nn.Module模块 读取Mnist数据集 会自动进行下载 %matplotlib inlinefrom pathlib import Path import requestsDATA_PATH Path("data") PATH DATA_PATH / &…

使用 Nacos 在 Spring Boot 项目中实现服务注册与配置管理

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

每日刷题(回溯法经典问题之子集)

食用指南&#xff1a;本文为作者刷题中认为有必要记录的题目 前置知识&#xff1a;回溯法经典问题之组合 ♈️今日夜电波&#xff1a;想着你—郭顶 1:09 ━━━━━━️&#x1f49f;──────── 4:15 …

jmeter setUp Thread Group

SetUp Thread Group 是一种特殊类型的线程组&#xff0c;它用于在主测试计划执行之前执行一些初始化任务。 SetUp Thread Group 通常用于以下几种情况&#xff1a; 用户登录&#xff1a;在模拟用户执行实际测试之前&#xff0c;模拟用户登录到系统以获取访问权限。 创建会话&a…

freertos之资源管理

中断屏蔽 屏蔽中断函数 在任务中使用 taskENTER_CRITICA()/taskEXIT_CRITICAL() 在中断中使用 taskENTER_CRITICAL_FROM_ISR()/taskEXIT_CRITICAL_FROM_ISR() 功能介绍 使用上述函数&#xff0c;进入临界中断&#xff0c;任务不会切换&#xff0c;且中断优先级处于con…

Ubuntu入门04——目录与文件

目录 1.显示当前工作目录 2.更改目录 3.创建工作目录 4.删除工作目录 5.移动文件或者文件夹 6.文件夹and文件查看命令 7. 回到根目录&#xff0c;回到上一级 8.删除工作目录 9.查看目录和文件 10.以树状图列出目录内容 11.文件查找 12.在数据库中查找文件或目录 1…

【大数据】Apache Iceberg 概述和源代码的构建

Apache Iceberg 概述和源代码的构建 1.数据湖的解决方案 - Iceberg1.1 Iceberg 是什么1.2 Iceberg 的 Table Format 介绍1.3 Iceberg 的核心思想1.4 Iceberg 的元数据管理1.5 Iceberg 的重要特性1.5.1 丰富的计算引擎1.5.2 灵活的文件组织形式1.5.3 优化数据入湖流程1.5.4 增量…

[移动通讯]【Carrier Aggregation-3】【5G】

前言&#xff1a; 参考&#xff1a; 5G Mobile Communications&#xff1a;《Carrier Aggregation in 5G》 目录&#xff1a; 1&#xff1a; carrier Allocation Schemes 2&#xff1a; 网络结构 3&#xff1a; LTE CA 4: 5G CA 一 Carrier Allocation Schemes CA 主要作用…

数学建模--非整数规划求解的Python实现

目录 1.算法流程简介 2.算法核心代码 3.算法效果展示 1.算法流程简介 #非线性规划模型求解: #我们采用通用的minimize函数来求解 #minimize(f,x,method,bounds,contrains) #f是待求函数 #x是代求的自变量 #method是求解方法 #bounds是取值范围边界 #contrains是约束条件 &q…

Java HashMap

简介 HashMap 是一个散列表&#xff0c;它存储的内容是键值对(key-value)映射。 HashMap 实现了 Map 接口&#xff0c;根据键的 HashCode 值存储数据&#xff0c;具有很快的访问速度&#xff0c;最多允许一条记录的键为 null&#xff0c;不支持线程同步。 HashMap 是无序的&…

新材料行业CRM客户管理系统的选择

新材料行业虽属小众细分市场&#xff0c;但其应用极广。更广阔的市场也意味着更激烈的竞争。新材料行业巨头林立&#xff0c;企业如何能脱颖而出&#xff1f;这里有一个适合新材料行业CRM系统推荐—Zoho CRM&#xff0c;帮助新材料企业推动业务高效运转的新模式。 数字化是企业…

Python爬虫乱码问题之encoding和apparent_encoding的区别

encoding是从http中的header中的charset字段中提取的编码方式&#xff0c;若header中没有charset字段则默认为ISO-8859-1编码模式&#xff0c;则无法解析中文&#xff0c;这是乱码的原因 apparent_encoding会从网页的内容中分析网页编码的方式&#xff0c;所以apparent_encodi…

Ubuntu之apt-get系列--apt-get安装软件的方法/教程

原文网址&#xff1a;Ubuntu之apt-get系列--apt-get安装软件的方法/教程_IT利刃出鞘的博客-CSDN博客 简介 本文介绍Ubuntu使用apt-get安装软件的方法。 安装软件 先更新列表 sudo apt-get update 安装软件 sudo apt-get install <package name>[<version>]…