ThreadLocal

news2025/2/27 14:02:45

文章目录

  • 一、ThreadLocal是什么
  • 二、ThreadLocal作用
  • 三、ThreadLocal的设计结构
    • 早期:
    • 现在:
  • 四、ThreadLocal核心方法
    • 1. set方法
    • 2. get方法
    • 3. remove方法
  • 五、ThreadLocal内存泄漏
  • 六、使用场景
  • 七、参考资料


前言:
再写博客时,遇到了如何处理保存用户的信息时出现混乱的问题(线程安全问题),在一番查找后,寻到了ThreadLocal用来保存登录的用户的信息的方法。


一、ThreadLocal是什么

在讲ThreadLocal前,先来看一下什么是线程封闭

封闭对应的是开放,所谓线程开放就是类似共享数据、共享变量这些概念。
多线程访问共享可变数据时,涉及到线程间数据同步问题。并不是所有时候,都要用到共享数据,所以线程封闭的概念就提出来了。

当访问共享的可变数据时,通常需要使用同步。一种避免使用同步的方式就是不共享数据。如果仅在单线程内访问数据,就不需要同步。这种技术被称为线程封闭。它是实现线程安全最简单的方式之一。当某个对象封闭在一个线程中时,这种用法将自动实现线程安全性。即使被封闭的对象本身不是线程安全的。

那么,要如何维持线程封闭呢,这时我们就可以使用ThreadLocal来使线程中的某个值与保存值的对象关联起来。

ThreadLocal说简单点就是往一个线程存数据,这个数据只能由这个线程获取,其他线程无法获取这个数据
ThreadLocal就是用空间换时间,Synchronized就是时间换空间

在这里插入图片描述
此图对于理解ThreadLocal十分重要


二、ThreadLocal作用

public class th {
    private String content;

    private String getContent() {
        return content;
    }

    private void setContent(String content) {
        this.content = content;
    }

    public static void main(String[] args) {
        th demo = new th();
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    demo.setContent(Thread.currentThread().getName() + "的数据");
                    System.out.println("-----------------------");
                    System.out.println(Thread.currentThread().getName() + "--->" + demo.getContent());
                }
            });
            thread.setName("线程" + i);
            thread.start();
        }
    }
}

在这里插入图片描述
在实际运行后,便出现了如上的结果,线程之间不隔离,每个线程获取的数据出现了错误,使用ThreadLocal,便可以很好的解决此问题。

ThreadLocal作用便是提供线程内的局部变量,不同的线程之间不会相互干扰,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或组件之间一些公共变量传递的复杂度。

  1. 线程并发: 在多线程并发的场景下
  2. 传递数据: 我们可以通过ThreadLocal在同一线程,不同组件中传递公共变量
  3. 线程隔离: 每个线程的变量都是独立的,不会互相影响

三、ThreadLocal的设计结构

早期:

在这里插入图片描述

现在:

在这里插入图片描述

每个Thread维护一个ThreadLocalMap,这个Map的keyThreadLocal实例本身,value才是真正要存储的值Object

每个Thread线程都有一个ThreadLocalMap(Map结构,但是不是直接使用的Map)
ThreadLocalMap中储存了key(ThreadLocalMap对象)和value(Object)
ThreadLocalMap由ThreadLocal维护,意思就是由ThreadLocal负责获取和设置 每一个线程互不干扰,形成隔离

好处:

  • 每个Map存储的Entry数量就会变少。因为之前的存储数量由Thread的数量决定,现在是由ThreadLocal的数量决定。在实际运用当中,往往ThreadLocal的数量要少于Thread的数量。
  • Thread销毁之后,对应的ThreadLocalMap也会随之销毁,能减少内存的使用。

四、ThreadLocal核心方法

静下心来看源码

1. set方法

    public void set(T value) {
        Thread t = Thread.currentThread();  //获取当前线程对象
        ThreadLocalMap map = getMap(t);  //获取此线程对象中维护的ThreadLocalMap对象
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);
        }
    }

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    
    void createMap(Thread t, T firstValue) {
         //  this是调用此方法的threadLocal
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

2. get方法

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            // 以当前的ThreadLocal 为 key,调用getEntry获取对应的存储实体e
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;   // 取值
                return result;
            }
        }
        return setInitialValue();   // 设置初始化值
    }   
    private T setInitialValue() {
        T value = initialValue(); // 获取初始化值 初为null,可重写
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t); // 获取此线程对象中维护的ThreadLocalMap对象
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);
        }
        if (this instanceof TerminatingThreadLocal) {
            TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
        }
        return value;
    }

    private Entry getEntry(ThreadLocal<?> key) {
         int i = key.threadLocalHashCode & (table.length - 1);
         Entry e = table[i];
         if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }
        
     protected T initialValue() {
           return null;
        }

3. remove方法

     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null) {
             m.remove(this);
         }
     }

五、ThreadLocal内存泄漏

强引用:只要强引用存在,垃圾回收器将永远不会回收被引用的对象,哪怕内存不足时,JVM也会直接抛出OutOfMemoryError,不会去回收。如果想中断强引用与对象之间的联系,可以显示的将强引用赋值为null,这样一来,JVM就可以适时的回收对象了.

软引用:在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。

弱引用:无论内存是否足够,只要 JVM 开始进行垃圾回收,那些被弱引用关联的对象都会被回收。

虚引用:如果一个对象仅持有虚引用,那么它就和没有任何引用一样,它随时可能会被回收。

内存泄漏:指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

在这里插入图片描述
一个线程属于强引用,对于一整个生命周期
当key为强引用时,key和value会一直存在,Entry不会被回收,从而导致内存泄漏
当key为弱引用时,在线程的生命周期中进行了一次垃圾回收,此时key作为弱引用被干掉了,但是整个线程还是存在,此时在Map中只有value,便会永远存在于内存中,当有大量的此行为时,便可能会造成内存泄漏

我们得知两个引用都有风险,但是为什么选择了弱引用呢?
我们是通过ThreadLocal来对ThreadLocalMap进行操作的,假设使用ThreadLocal的对象被设置为null了,那ThreadLocalMap的强引用指向ThreadLocal也毫无意义。弱引用反而可以预防大多数内存泄漏的情况,毕竟被回收后,下一次调用set/get/remove时ThreadLocal内部会清除掉


六、使用场景

1.用于保存线程不安全的工具类,如SimpleDateFormat
2.每个线程需要独立保存信息,如个人博客中保存的用户信息


七、参考资料

https://www.zhihu.com/question/399087116/answer/2763547159
https://www.bilibili.com/video/BV1QS4y1s7hi
https://blog.csdn.net/LemonSnm/article/details/122251312
https://www.cnblogs.com/liyutian/p/9690974.html

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

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

相关文章

基于共享储能电站的工业用户 日前优化经济调度matlab程序(yalmip+cplex)(yalmip+gurobi)

基于共享储能电站的工业用户 日前优化经济调度matlab程序&#xff08;yalmipcplex&#xff09;&#xff08;yalmipgurobi&#xff09; 参考文献&#xff1a;基于共享储能电站的工业用户 日前优化经济调度 摘要: 文章提出一种基于共享储能电站的工业用户日前优化经济调度方法。…

nginx反向代理,负载均衡配置

文章目录一.nginx代理简介二.nginx配置简介三.nginx作为反向代理的配置四.nginx作为负载均衡的配置五.使用nginx代理的坑一.nginx代理简介 其实nginx作为代理有两种 正向代理: 隐藏客户端的信息;如科学上网 反向代理: 隐藏服务端的信息;如负载均衡 二.nginx配置…

11.26

目录 一.做题出错 1. 2.数组长度的一半 3.选择题 二.优先级队列(堆) 1.二叉树的顺序存储 1.1 存储方式 1.2下标关系 2.堆(heap) 2.1概念 2.2 操作-向下调整 三 建堆 四.优先级队列 1 概念 2 内部原理 3.操作-入队 offer() 4.操作-出队 五.计算糖果 一.做题出错…

docker如何下载国外镜像

目录背景解决方案1、创建阿里云镜像仓库2、使用https://labs.play-with-docker.com下载镜像3、将镜像上传到阿里云镜像仓库4、从阿里云镜像仓库中拉取镜像到我们linux系统中5、改变我们linux系统中拉取的镜像名称背景 今天在安装grafana和prometheus&#xff0c;但是在下载下面…

Java数据结构

目录 1、栈 2、队列 3、数组 4、链表 5、树 7、平衡二叉树 8、红黑树 1、栈 特点&#xff1a;先进后出&#xff0c;后进先出 数据进入栈模型的过程称为:压/进栈 数据离开栈模型的过程称为:弹/出栈 2、队列 特点&#xff1a;先进先出&#xff0c;后进后出 数据从后…

MyBatis-Plus中的更新操作(通过id更新和条件更新)

目录 前言 一、通过id更新 二、条件更新 2.1 使用QueryWrapper进行条件更新 2.2 使用UpdateWrapper进行条件更新 总结 前言 本文学习MP中的更新操作方法&#xff0c;带大家一起查看源码&#xff0c;了解更新操作的方法。学会熟练地去运用更新方法解决自己在项目中的问题…

Linus 文件处理(三)

目录 一、前言 二、扫描目录 1、opendir 2、readdir 3、telldir 4、seekdir 5、 closedir 6、A Directory-Scanning Program 三、Errors 1、strerror 2、perror 一、前言 本文将简单介绍Linux文件和目录&#xff0c;以及如何操作它们&#xff08;如何创建文件、打开…

独家 | 使用python马尔科夫链方法建模星巴克等待时长

作者&#xff1a;Piero Paialunga翻译&#xff1a;陈超校对&#xff1a;和中华本文约4200字&#xff0c;建议阅读11分钟本文使用马尔科夫链的方法对星巴克购买咖啡的等待时长进行建模。以下内容关于如何使用马尔科夫链计算你在星巴克咖啡的等待时长。图片来自Unplash&#xff0…

Spring - ApplicationContextInitializer 扩展接口

文章目录Preorg.springframework.context.ApplicationContextInitializer扩展点扩展接口扩展生效方式方式一 &#xff1a; Spring SPI扩展方式二 &#xff1a; 配置文件方式三 &#xff1a;启动类手工add测试结果Pre Spring Boot - 扩展接口一览 org.springframework.context.…

详解诊断数据库ODX-C

文章目录 前言一、ODX—C作用是什么?二、ODX-C数据库在工具ODXStudio的编辑方法总结前言 ODX是全球通用的一种诊断数据库格式,相比CDD文件(Vector公司私有的一种数据库格式),应用场景和范围更广,包含了不同的子类: ODX-C\-D\-V\-E\-F\-FD 今天这篇文章仅对ODX-C做一个…

开源物联网系统 ThingsBoard 上手

开源物联网系统 ThingsBoard 上手 centos yum 被占用问题解决&#xff1a; 描述&#xff1a;Another app is currently holding the yum lock; waiting for it to exit 参考&#xff1a;https://blog.csdn.net/Dan1374219106/article/details/112450922 查看yum占用&#xff1a…

制作一个简单HTML中华传统文化网页(HTML+CSS)

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

scrapy的入门使用

目录 一、 安装scrapy 1.windonws/Mac安装命令&#xff1a; 2. 安装依赖包&#xff1a;pip install pypiwin32 二、 scrapy项目开发流程 1.创建项目:    2.生成一个爬虫: 3.提取数据: 4.保存数据: 三、 创建项目 四、创建爬虫 五、完善爬虫 5.2 定位元素以及提取…

序列化与反序列化笔记

序列化与反序列化 为什么会有序列化与反序列化的需求呢&#xff1f; 序列化是把对象转换成有序字节流&#xff0c;通常都是一段可阅读的字符串&#xff0c;以便在网络上传输或者保存在本地文件中。同样&#xff0c;如果我们想直接使用某对象时&#xff0c;就可能通过反序列化…

23-Vue之事件修饰符

事件修饰符前言阻止默认行为阻止冒泡事件前言 本篇来学习两个常用的事件修饰符 阻止默认行为 .prevent : 阻止默认行为 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" cont…

基于JSP的网上书城平台【数据库设计、源码、开题报告】

数据库脚本下载地址&#xff1a; https://download.csdn.net/download/itrjxxs_com/86469277 主要使用技术 ServletJDBCJSPC3P0JqueryMysql 功能介绍 1). 用户模块功能有&#xff1a; 用户注册: 表单页面是jQuery做校验(包含了ajax异步请求) 表单页面使用一次性图形验证码…

分布式事务Seata

目录 一、分布式事务的认识 事务的ACID原则 CAP定理 BASE理论 二、Seata简介、安装和部署 1.初识Seata 2.下载Seata&#xff08;1.4.2版本&#xff0c;其他版本可能与本章教程冲突&#xff09; 3.修改配置文件 4.在nacos添加配置 5.创建配置文件中的数据库表 6.启动TC…

二、微服务拆分案例

文章目录一、服务拆分&#xff08;order-service、user-service&#xff09;1.创建数据库2.创建order-service和user-service模块&#xff0c;引入依赖3、order-service各层代码4、user-service各层代码一、服务拆分&#xff08;order-service、user-service&#xff09; 1.创建…

猴子也能学会的jQuery第十二期——jQuery遍历

&#x1f4da;系列文章—目录&#x1f525; 猴子也能学会的jQuery第一期——什么是jQuery 猴子也能学会的jQuery第二期——引用jQuery 猴子也能学会的jQuery第三期——使用jQuery 猴子也能学会的jQuery第四期——jQuery选择器大全 猴子也能学会的jQuery第五期——jQuery样式操作…

【金融项目】尚融宝项目(十五)

29、提现和还款 29.1、提现 29.1.1、需求 放款成功后&#xff0c;借款人可以申请提现。 参考《汇付宝商户账户技术文档》3.15用户申请提现 29.1.2、前端整合 pages/user/withdraw.vue <script> export default {data() {return {fetchAmt: 0,}},methods: {commitWit…