三问 ThreadLocal —— 有什么用 ? 使用时有什么潜在风险?原理 ?

news2024/12/24 3:37:32

最近想实现一些功能,求诸于网络之后,得到了使用 ThreadLocal 实现的方式,那么 ThreadLocal 到底是什么呢 ? 遂写此文,抽丝剥茧的来看一下这个 Java 并发类 。

最近,me 的 gpt 账号没了,所以不能让 gpt 帮 me 美化语言了,大家将就看吧…

一、看看作者怎么说 😏 —— 有什么用篇

(ps:大家再去看这些 Java 原生功能,就别一顿乱搜了呗,当然万能的GPT除外,呜呜呜,JDK 源码里有作者的介绍,看看作者怎么说,可以防止我们走进误区)

在这里插入图片描述

作为 ThreadLocal 0基础小白,我带大家读一下哈 …

1. 首先

首先,作者告诉我们,ThreadLocal 提供了一个 thread-local 的变量,也就是以线程为单位的一个线程的局部变量 , 访问这个变量的每个线程都有属于自己的这个变量的副本 。

2. 接着

接着呢,作者告诉我们,这个 ThreadLocal 通常被类定义为 static 的 (为什么定义成 static 的,我们在后面说,现在先大概了解一下),并且这个线程和某些状态相关联,这些状态包括某些用户的 ID 或者 某些事务的 ID 等 。 但是这样直接翻译,我觉得不是很信达雅,我觉得这么说比较好,执行某个操作的线程需要携带某些状态,比如需要携带用户 ID,这时候我们就可以使用 ThreadLocal 。

3. 然后

然后,作者给我们举了个使用例子 。

4. 最后

最后,作者和我们说了一下,ThreadLocal 实例的生命周期,它和持有它的线程共存亡,持有它的线程活着,它就活着,否则,它就会被垃圾回收掉 。

总结

通过上面作者说的话,我们可以大概了解 ThreadLocal 是什么了,下面我尝试用一句话概括 —— ThreadLocal 是我们某个线程独占的变量 , 其可以携带某些状态,这些状态对当前线程有用,我们就带上它,然后它和线程共存亡,线程 g 了, 它被垃圾回收掉 。

(ps : 感觉我说话太不正经了,大家平时别这么说话,给人一种半吊子的感觉 , 会掉印象分的,QWQ)


二、原理篇

我不想带着大家看源码了,源码太长了,这里取几个我觉得重要的点说一下 :

  1. 每个 Thread 里都有一个ThreadLocal.ThreadLocalMap, 我们定义的 ThreadLocal 变量就存放在这个 map 里,其中 ThreadLocal 对象作为 key, ThreadLocal 对象携带的 value 作为值,形成一个个 Entry —— 之所以共生命周期的原因

在这里插入图片描述

在这里插入图片描述

  1. ThreadLocal 在遍历上面的 map 执行 get 和 set 方法时候,会自动帮我们释放 key 为 null 的 value 的空间

三、潜在风险篇

在上文,我们了解到了 ThreadLocal 是什么,以及它的实现原理。然后,我已经决定使用它作为我的当前业务代码的解决方案,所以,我必须做一件事 —— 分析一下是不是可能有潜在风险,不然万一我走了,代码给后人维护,然后突然出了个大 bug,那个方法上面赫然写着我的名字,我可不想我都走了,还要被人骂 …

下面是我画的一张 ThreadLocal 的内存分析图,毕竟一图胜过千言嘛 ~ 但是,要理解下面的图,需要知道以下几个概念 :

  1. JVM 内存模型中的 栈 和 堆, 栈内存中存放对象的引用,堆内存中存放对象的实例 ,对应下图,ThreadLocal 类的引用和实例, 以及当前线程类的引用和实例 。
  2. 引用就是指针,虽然 Java 中没有指针的概念,但是可以这么去理解,引用指向实例。
  3. Java 中对象的引用分为强引用、软引用、弱引用 和 虚引用
  4. Java 垃圾回收,找到需要回收的对象的方法 —— 可达性分析法,翻译过来就是没有引用的对象直接回收。具有弱引用的对象进行垃圾回收时,扫描到就直接回收。但是,对于强引用,宁可内存溢出也不能回收强引用对象,我们平时创建的普通对象就是强引用的,Java 宁可发生 OOM 也不能释放我们还没用完的强引用对象 。
  5. 内存泄漏是值:动态分配的堆内存因为某些原因未被释放,但是过多的内存泄漏会导致内存溢出

在这里插入图片描述
通过上面的分析,我们可以得出之所以会出现内存泄漏,是因为没有手动释放 value 造成的,所以解决方式也是不要等我们的 ThreadLocal 通过 get 或者 set 自动删除,而是每次都手动 remove


四、尝试用一下

按照作者的推荐,其适合用来为一个线程存放事务ID或者用户ID,从而使得这个ID可以被当前线程所把持,不存在被其他线程修改的线程安全问题 。

功能描述

我的一个业务功能需要获取当前登录的用户的信息,这个用户信息我存放在 Session 中了。我希望可以实现下面的链路 :

所以,我使用 ThreadLocal 来解决,每个请求相当于一个线程,我为这个线程封装好 ThreadLocal, 其 value 是用户相关的信息 。
在这里插入图片描述

1. 在当前业务拦截器中将用户信息放入 ThreadLocal

public class CartInterceptor implements HandlerInterceptor {
    public static ThreadLocal<UserInfoTO> threadLocal = new ThreadLocal<>();
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 获取会话信息,获取登录用户信息
        HttpSession session = request.getSession();
        MemberResponseVO attribute = (MemberResponseVO) session.getAttribute(AuthConstant.LOGIN_USER);
        // 判断是否登录,并封装User对象给controller使用
        UserInfoTO user = new UserInfoTO();
        if (attribute != null) {
            // 登录状态,封装用户ID,供controller使用
            user.setUserId(attribute.getId());
        }
        // 获取当前请求游客用户标识user-key
        Cookie[] cookies = request.getCookies();
        if (ArrayUtils.isNotEmpty(cookies)) {
            for (Cookie cookie : cookies) {
                if (cookie.getName().equals(CartConstant.TEMP_USER_COOKIE_NAME)) {
                    // 获取user-key值封装到user,供controller使用
                    user.setUserKey(cookie.getValue());
                    user.setTempUser(true);// 不需要重新分配
                    break;
                }
            }
        }

        // 判断当前是否存在游客用户标识
        if (StringUtils.isBlank(user.getUserKey())) {
            // 无游客标识,分配游客标识
            user.setUserKey(UUID.randomUUID().toString());
        }
        // 封装用户信息(登录状态userId非空,游客状态userId空)
        threadLocal.set(user);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        UserInfoTO user = threadLocal.get();
        if (user != null && !user.isTempUser()) {
            // 需要为客户端分配游客信息
            Cookie cookie = new Cookie(CartConstant.TEMP_USER_COOKIE_NAME, user.getUserKey());
            cookie.setDomain("gulimall.com");// 作用域
            cookie.setMaxAge(CartConstant.TEMP_USER_COOKIE_TIMEOUT);// 过期时间
            response.addCookie(cookie);
        }
    }
}

2. 在业务要使用时,拿出 ThreadLocal 中的用户信息进行判断

    @Override
    public List<CartItemVO> getUserCartItems() {
        // 获取当前用户登录的信息
        UserInfoTO userInfo = CartInterceptor.threadLocal.get();
        if (userInfo.getUserId() == null) {
            // 未登录
            return null;
        } else {
            // 已登录,查询redis用户购物车
            List<CartItemVO> items = getCartItems(CartConstant.CART_PREFIX + userInfo.getUserId());
            if (CollectionUtils.isEmpty(items)) {
                throw new CartExceptionHandler();
            }
            // 筛选所有选中的sku
            Map<Long, CartItemVO> itemMap = items.stream().filter(item -> item.getCheck())
                    .collect(Collectors.toMap(CartItemVO::getSkuId, val -> val));
            // 调用远程获取最新价格
            Map<Long, BigDecimal> priceMap = productFeignService.getPrice(itemMap.keySet());
            // 遍历封装真实价格返回
            return itemMap.entrySet().stream().map(entry -> {
                CartItemVO item = entry.getValue();
                item.setPrice(priceMap.get(entry.getKey()));// 封装真实价格
                return item;
            }).collect(Collectors.toList());
        }
    }

思考其他实现方案 对比 ThreadLocal

每次要用到 user 信息的时候,我都去读一遍 Session 然后取出来用 —— 代码冗余量太大,而且多线程并发下,可能会产生线程安全问题 (要进行控制,但是同步之后又会影响并发量)。

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

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

相关文章

【模板】Hexo Docker Nginx 个人博客服务器部署

上文&#xff1a;基于 Hexo 的 Github 博客搭建 注意&#xff1a;通过验证部署&#xff0c;确定无误。AI生成的部分有&#x1f916;图标。 &#x1f916; TLDR By ChatGPT 本指南提供了在服务器上设置Git仓库、将本地Hexo页面推送到服务器仓库、在服务器上创建Nginx配置文件以…

数字信封例程不支持的bug,以及卸载安装配置Node.js

文章目录 前言一、运行错误:0308010C:数字信封例程:不支持二、卸载Node.js三、重新安装Node.js总结 前言 下载了若依项目&#xff0c;但是在前端项目运行打包都出现了bug。最后&#xff0c;卸载了Node.js&#xff0c;并重新安装了低版本的Node.js。 一、运行错误:0308010C:数字…

[算法前沿]--003-AGI通用人工智能模型对安全的影响和开源的大模型

文章目录 0.ChatGPT大模型带来的影响0.1 ChatGPT带来信息化革命性创新&#xff0c;目前尚不能处理专业知识但成长很快0.2 Chat GPT为网安行业带来新的创新方向&#xff0c;也将引领新一轮投融资热潮0.2.1 攻击方发起网络攻击的门槛降低0.2.2 防守方合理使用ChatGPT可大幅减少安…

组态王与PLC之间1主多从自组网无线通信

本方案是基于三菱专用协议下实现的1主多从自组网无线通信形式&#xff0c;主站为组态王&#xff0c;从站为三菱FX3U PLC和485BD扩展。采用日系PLC专用无线通讯终端DTD435MC-V96&#xff0c;作为实现无线通讯的硬件设备&#xff0c;来解决组态王与PLC之间的通讯问题。 一、方案…

百度AI模型“文心一言”新鲜体验

今天收到通知可以体验百度的AI模型“文心一言”&#xff0c;等了一个多月迫不及待的去体验了一把&#xff0c;以下是体验的相关记录。 1、简单介绍 通过文心一言官网链接https://yiyan.baidu.com/进入&#xff0c;看到如下界面&#xff1a; 在文心一言的自我介绍中&#xff0c…

seetaface6 GPU版本windows编译

目录 1. seetaface6概述2. 编译说明2.1 编译工具2.2 编译顺序 3. 编译OpenRoleZoo4. 编译SeetaAuthorize5. 编译TenniS6. 编译FaceTracker6 1. seetaface6概述 seetaface6源码以及模型文件github下载路径&#xff1a;https://github.com/SeetaFace6Open/index 由于项目性能需求…

华为为何要搞相对冷门的ERP?

大家都知道华为的研发实力很强&#xff0c;但几天前他们宣布研发出超大规模云原生的ERP时&#xff0c;还是有些吃惊。 20日&#xff0c;在东莞的一场“英雄强渡大渡河”表彰会上&#xff0c;华为抛出一个大多数公司都难以做到的成果&#xff1a;三年时间&#xff0c;数千人&am…

从零开始写ChatGLM大模型的微调代码

cursor 的下载及安装&#xff08;免费版每月100次&#xff0c;升级pro 20刀/月&#xff09; cursor是一款与openai合作的&#xff0c;使用gpt-4的一款编程工具&#xff0c;它可以让你通过gpt-4进行辅助编程&#xff0c;以此提高效率。 下载地址&#xff1a;https://www.curso…

CentOS系统安装Intel E810 25G网卡驱动

因特尔网卡驱动给的都是二进制包&#xff0c;需要编译环境。 首先去Intel下载最新的驱动 E810驱动下载&#xff1a;https://www.intel.com/content/www/us/en/download/19630/intel-network-adapter-driver-for-e810-series-devices-under-linux.html?wapkwe810 里面有三个驱…

量子计算新进展!瑞典和芬兰率先研发芯片

&#xff08;图片来源&#xff1a;网络&#xff09; 芬兰和瑞典在北欧率先推出了独立的量子计算机项目&#xff0c;并在未来量子技术方面取得了可喜的新进展。 在瑞典&#xff0c;查尔姆斯理工大学 (Chalmers UoT) 获得了追加的900万欧元&#xff08;约合6774万元人民币&#x…

风力发电系统的随机调度研究(matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

【神经网络】tensorflow实验8--梯度下降法

1. 实验目的 ①掌握TensorFlow的可训练变量和自动求导机制 ②能够使用TensorFlow实现梯度下降法&#xff0c;求解一元和多元线性回归问题 2. 实验内容 下载波士顿房价数据集&#xff0c;使用线性回归模型实现对波士顿房价的预测&#xff0c;并以可视化的形式输出模型训练的过…

【Git】—— 版本控制(忽略文件、查看修改内容,撤销未提交的修改、查看历史提交记录)

目录 一、准备工作 二、忽略文件 三、查看修改内容 四、撤销未提交的修改 五、查看提交记录 前面学习了Git最基本的用法&#xff0c;包括安装Git、创建代码仓库以及提交本地代码。 下面将要学习版本控制。 一、准备工作 先选择一个项目&#xff0c;给它创建一个代码仓库&a…

Linux环境定时备份MySQL数据库

数据库备份常有&#xff0c;实现备份&#xff0c;大概需要3个步骤&#xff1a; 目录 1.创建数据库备份用户 2.创建数据库备份shell脚本 3.创建定时任务 1.创建数据库备份用户 为了数据安全&#xff0c;不建议使用root用户。举例&#xff1a;创建数据库备份用户 dump&#…

复现永恒之蓝[MS17_010]

目录 准备靶机 测试ping连通性 攻击漏洞 利用漏洞 准备靶机 1台kali&#xff0c;1台win7 win7系统可以在MSDN镜像网站里获取 注:将win7安装好&#xff0c;win7无法安装vmtools&#xff0c;若升级系统&#xff0c;可能会把永恒之蓝补丁打上&#xff0c;所以建议别升级系统 测试…

用Java创建可扩展的OpenAI GPT应用程序

ChatGPT 值得深入使用的方面之一是它的引擎&#xff0c;它不仅为基于Web的聊天机器人提供动力&#xff0c;还可以集成到Java应用程序中。 ▌Budget Journey App 想象一下&#xff0c;你想去一个城市旅行并且设置好了预算&#xff0c;你应该如何分配你的钱并让你的旅行难忘&am…

巧用千寻位置GNSS软件|逐点放样应用技巧

线路逐点放样是针对施工需要和设计要求&#xff0c;对线路 20、50、100间隔的整桩距或整桩号的特定桩位进行连续放样设定的程序。运用千寻位置GNSS软件如何快速实现线路逐点放样呢&#xff1f;让我们一探究竟吧&#xff01; 点击【测量】->【线路逐点放样】&#xff0c;选择…

CMake : Linux 搭建开发 - g++、gdb

目录 1、环境搭建 1.1 编译器 GCC&#xff0c;调试器 GDB 1.2 CMake 2、G 编译 2.1 编译过程 编译预处理 *.i 编译 *.s 汇编 *.o 链接 bin 2.2 G 参数 -g -O[n] -l、-L -I -Wall、-w -o -D -fpic 3、GDB 调试器 3.1 调试命令参数 4、CMake 4.1 含义 4.2…

npm全局包及项目包的讲解与方法

看图可知 安装全局包和项目包 我们通过npm install 命令来安装包&#xff0c;简单说就是把包从npm的官网&#xff08;或者是指定的镜像源&#xff09;下载到我们自己的电脑中。那具体这个包下载到哪里了&#xff0c;还是有一点讲究的。 分成两类&#xff1a; 全局安装: 包被…

IntelliJ IDEA 新建安卓项目失败,网上的各种教程都不管用的看向这里!!!

目录 1、出现的问题&#xff1a; 2、解决办法&#xff1a; 1&#xff09;新建项目 2&#xff09;修改设置 3、注意 备注&#xff1a; 目的&#xff1a;最近开发机器人远程控制需要一个安卓app&#xff0c;之前是自己做了个H5app来用的&#xff0c;H5不是很稳定&#xff0c;所…