Java并发编程 —— ThreadLocal详解

news2024/12/24 3:02:41

一、什么是ThreadLocal

ThreadLocal用于提供线程内部共享的变量,每个线程在访问ThreadLocal实例的时候都可以获得自己的、独立初始化的变量副本,这样线程间互不干扰,从而避免了线程安全问题。

比如我们知道SimpleDateFormat是线程不安全的,多个线程同时用一个SimpleDateFormat对象解析日期时间会报错,但是在一个线程中每解析一个数据就创建一个SimpleDateFormat对象显然也很浪费,这时候我们就可以通过ThreadLocal.withInitial()方法创建一个ThreadLocal实例,并设定变量初始化函数,那么每个线程在第一次调用get()方法时就会执行这个初始化创建自己的SimpleDateFormat对象并返回,之后在线程中每次调用get()都是返回这个自己的独立的对象,从而实现了线程内的变量共享,并且线程间互不干扰。

public class DateFormatTest {
    public static void main(String[] args) {
        for (int i=1;i<=3;i++) {
            new Thread(() -> {
                try {
                    System.out.println(CommonUtils.parseTime("12:23:11"));
                } catch (ParseException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}
class CommonUtils {
    public static ThreadLocal<SimpleDateFormat> safeSdf =
            ThreadLocal.withInitial(() -> new SimpleDateFormat("hh:mm:ss"));
    public static long parseTime(String timeStr) throws ParseException {
        return safeSdf.get().parse(timeStr).getTime();
    }
}

除了withInitial()方法外,还可以直接调用ThreadLocal默认构造器创建实例,然后在需要设置线程共享变量时调用set()方法直接设置。

比如在请求Java后端的API服务时,http请求中经常会携带一些通用参数,比如token、uid、did这些,我们可以在请求预处理时将这些数据解析出来通过set()方法放到ThreadLocal中,然后在这个请求处理过程中此线程任意位置都可以直接访问到这些数据了,最后在请求结束后调用remove将数据移除就可以了。

public class TokenTL {
    public static ThreadLocal<String> tokenTL= new ThreadLocal<>();
    public static void setToken(String token) {tokenTL.set(token);}
    public static String setToken() {return tokenTL.get();}
    public static void removeToken() {tokenTL.remove();}
}

二、ThreadLocal的基本原理

线程Thread类中有一个ThreadLocalMap成员变量,一个线程所有通过ThreadLocal创建的独立变量实际上就是存放在这里面。

public class Thread implements Runnable {
    //......
    //与此线程有关的ThreadLocal值。由ThreadLocal类维护
    ThreadLocal.ThreadLocalMap threadLocals = null;

    //与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    //......
}

ThreadLocalMap本身是ThreadLocal的一个内部类,可以把它理解为ThreadLocal 类实现的定制化的 HashMap。内部也是通过一个Entry数组存储键值对,键是对ThreadLocal对象的弱引用,值就是存储的独立变量。

1. set()方法

调用threadLocal.set()方法时,首先获取当前线程中的ThreadLocalMap对象,如果为null则创建,然后以当前threadLocal弱引用为key,以要set的值为value,在这个map中插入或者更新值。

public void set(T value) {
    //获取当前请求的线程
    Thread t = Thread.currentThread();
    //取出 Thread 类内部的 threadLocals 变量(哈希表结构)
    ThreadLocalMap map = getMap(t);
    if (map != null)
        // 将需要存储的值放入到这个哈希表中
        map.set(this, value);
    else
        createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

2. get()

在调用threadLocal.get()方法时,就是在当前线程的ThreadLocalMap中以threadLocal实例引用为key查找对应的value,如果没找到,则看threadLocal实例有没有重写InitialValue()函数,就是前面说的withInitial()方法创建时设置的,如果有则初始化并设置value值,然后将其返回。

    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();
    }

3. remove()方法

调用threadLocal.set()方法时,清除当前线程的ThreadLocalMap中以当前ThreadLocal实例为key的Entry对象

三、ThreadLocal内存泄漏问题

线程中ThreadLocalMap对象的引用链如下:

Thread -> ThreadLocal.ThreadLocalMap -> Entry[] -> Enrty -> key(threadLocal对象)和value

其中key 为 ThreadLocal 对象的弱引用,而 value 是强引用。所以,如果这个 ThreadLocal 对象没有被外部强引用的情况下,在垃圾回收的时候,key 会被自动清理掉,而 value 不会被清理掉。

虽然在调用 set()、get()方法时清理部分 key 为 null 的记录,但这显然是不完备的,最好还是在使用完 ThreadLocal方法后手动调用remove()方法进行清除。

不过可能通常在使用ThreadLocal时都是直接定义为类变量(static修饰),默认被类强引用

1. 为什么 ThreadLocalMap 的 key 设计为弱引用?

在这里插入图片描述

2. 为什么 ThreadLocalMap 的 value 设计为强引用?

【假设Entry 的 value 是弱引用】:假设 key 所引用的 ThreadLocal 对象还被其他的引用对象强引用着,那么这个 ThreadLocal 对象就不会被 GC 回收,但如果 value 是弱引用且不被其他引用对象引用着,那 GC 的时候就被回收掉了,那线程通过 ThreadLocal 来获取 value 的时候就会获得 null,显然这不是我们希望的结果。因为对我们来说,value 才是我们想要保存的数据,ThreadLcoal 只是用来关联 value 的,如果 value 都没了,还要 ThreadLocal 干嘛呢?所以 value 不能是弱引用。

参考:https://zhuanlan.zhihu.com/p/513517989

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

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

相关文章

LVS负载均衡+keepalived高可用

准备准备五台虚拟机 192.168.255.128 MASTER 192.168.255.134 BACKUP 192.168.255.130 Nginx节点服务器1 192.168.255.131 Nginx节点服务器2 192.168.255.132 客户端验证 一、配置节点服务器 1、配置虚接口lo:0 cd /etc/sysconfig/network-scripts/ cp ifcfg-…

Vue-cli 3.x 脚手架搭建的 Vue 2.x 项目进行 npm install 安装时报错: npm ERR! code 1……

项目场景&#xff1a; 公司以前做过的项目&#xff0c;当时开发环境 npm&#xff0c;node.js 版本和现在都不一样&#xff0c;比较旧了&#xff0c;项目之前是用 Vue-cli 3.x 搭建的&#xff0c;Vue 2.x 写的&#xff0c;当时配套的第三方依赖版本也都很落后了&#xff0c;在近…

有趣有爱有温度!迅镭激光第一季度户外团建活动圆满落幕!

阳春四月&#xff0c;元气复苏 凝心聚力&#xff0c;共享喜悦 迅镭激光第一季度寿星及新人 欢聚常熟蒋巷基地 开启一段美妙的户外团建之旅 无创意不团建!蓝天白云下&#xff0c;队员们在教练的指导下解锁各种花样游戏大玩法&#xff0c;大家密切配合、相互协作&#xff0c;…

Docker | 解决docker 容器中csv文件乱码的情况

问题描述&#xff1a;在Ubuntu docker容器中&#xff0c;打开.csv文件时显示乱码 问题如图 错误分析&#xff1a; 用locale查看所用容器支持的字符集 从输出可以看到&#xff0c;系统使用的是POSIX字符集&#xff0c;POSIX字符集是不支持中韩文的&#xff0c;而UTF-8是支持中…

054:cesium加载WMS规范的影像服务

第054个 点击查看专栏目录 本示例的目的是介绍如何在vue+cesium中加载WMS规范的影像服务。WebMapServiceImageryProvider提供由 Web 地图服务 (WMS) 服务器托管的平铺图像。 直接复制下面的 vue+cesium源代码,操作2分钟即可运行实现效果. 文章目录 示例效果配置方式示例源代…

剑指 Offer II 026. 重排链表

思路&#xff1a; &#xff08;1&#xff09;找到链表中心点&#xff0c;如果链表节点为奇数&#xff0c;那么要保证前面要比后面多一个节点。 &#xff08;2&#xff09;将后一部分的结点进行反转。 &#xff08;3&#xff09;将反转后的结点插入前一部分的结点。 &#xff0…

前有谷歌的“生存指南”,后有金山系的“表格编程”,均登热榜

谷歌的“生存指南” 一位曾经在谷歌工作的工程师&#xff0c;干了一件了不起的事&#xff0c;花费了两年的时间&#xff0c;整理了一份“xg2xg”的清单。 原来这位离职的谷歌工程师为程序员编写了一份“厂外生存指南”&#xff0c;即使你从谷歌离职后&#xff0c;在这套“生存…

无良公司把我从上家挖过来,白嫖了六个月,临近试用期结束才说不合适,催我赶紧找下家!...

职场套路多&#xff0c;一不小心就会掉坑&#xff0c;一位网友讲述了自己的遭遇&#xff1a; 今天被领导催促离职了&#xff0c;当时就是这个领导把他从别的公司挖过来。这家公司催得太急&#xff0c;为了投奔这里&#xff0c;他和上家的HR都闹翻了&#xff0c;上家总监挽留他&…

ChatGLM ptuning 的实战方案

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的见解。曾经辅导过若干个非计算机专业的学生进入到算法…

【中间件】kafka

目录 一、概述二、生产者1. 发送原理2. 生产者分区 Partition分区好处分区策略 3. 生产者如何提高吞吐量4. 数据可靠性ACK应答级别数据不丢失&#xff1a;ACK ISR数据不重复&#xff1a;幂等性数据有序 三、broker1. 工作流程2. 副本相关3. 底层存储4. 高效读写数据 四、消费者…

常见误区,你可曾踩过?深度剖析WEB自动化测试实施问题与解决方案

目录 摘要&#xff1a; 一、WEB自动化测试实施的步骤 1.测试计划 2.测试用例设计 3.环境搭建 4.脚本编写 5.执行测试 6.缺陷管理 二、WEB自动化测试常见误区 1.选择不合适的自动化测试工具和框架 2.忽略测试环境的影响 3.. 缺乏对页面元素的理解 三、示例代码 结论…

学顶教育:一级消防师资格证发放问题分享

1、为什么证书不申请没加注就可以下载&#xff1f; 2021年12月17日前由相关行业协会、学会或有关部门指定的机构出具的电子证书&#xff0c;目前无需申请加注&#xff0c;可直接查看、下载。 2、如何查询“证书查询验证范围”&#xff1f; 进入中国人事考试网首页&#xff0…

体验 Google Bard

环境 windows 10 64bitGoogle Bardpython 3.8 简介 本篇介绍一个开源的 Google 聊天机器人Bard 的 API 逆向工程&#xff0c;使用它&#xff0c;可以免费的使用 Bard 服务&#xff0c;项目地址&#xff1a;https://github.com/acheong08/Bard 安装及使用 通过 pip 来安装 pip &…

内网渗透的一些tips

声明&#xff1a;文中涉及到的技术和工具&#xff0c;仅供学习使用&#xff0c;禁止从事任何非法活动&#xff0c;如因此造成的直接或间接损失&#xff0c;均由使用者自行承担责任。 每周不定时持续分享各种干货。 众亦信安&#xff0c;中意你啊&#xff01; 一.密码抓取 平…

CVPR 2023 | 一键去除视频闪烁,该研究提出了一个通用框架

该论文成功提出了第一个无需额外指导或了解闪烁的通用去闪烁方法&#xff0c;可以消除各种闪烁伪影。 高质量的视频通常在时间上具有一致性&#xff0c;但由于各种原因&#xff0c;许多视频会出现闪烁。例如&#xff0c;由于一些老相机硬件质量较差&#xff0c;不能将每帧的曝光…

字符函数和字符串函数(二)

目录 1.strncpy 2.strncat 3.strncmp 4.strstr 5.strchr 6.strtok 7.strerror 1.strncpy ok,在上一篇博客的讲解中&#xff0c;我们谈到了strcpy这个函数&#xff0c;并且了解到了这个函数的功能是将源字符串的内容复制到目标字符串里&#xff0c;并且我们还知道了在C语…

字符函数和字符串函数(三)

1.字符分类函数 在我们的日常代码生活中&#xff0c;经常会遇到一类问题&#xff0c;比如说判断一个字母的大小写&#xff0c;或者标点符号等。因此&#xff0c;我们需要用到一些字符分类函数 函数 如果他的参数符合下列条件就返回真 iscntrl 任何控制字符 isspace 空白字符&…

使用GPT4做Leetcode第 102 场双周赛

虽然一次周赛的几个题目说明不了太多问题&#xff0c;比如这个周赛的Hard题目就是板子题&#xff0c;算不上Hard&#xff0c;也许把第三题和第四题的顺序换一下比较合适。但是&#xff0c;GPT4的表现已经严重超出了我的预期。对于这次周赛的四个题目&#xff0c;GPT4的表现如下…

说走就走的接口自动化测试脚本:快速提高测试效率的秘密武器

目录 摘要&#xff1a; 环境准备 编写测试用例 运行测试脚本 总结 摘要&#xff1a; 作为一名测试工程师&#xff0c;我们常常需要进行接口测试&#xff0c;目的是验证接口是否符合规范并且稳定可靠。然而&#xff0c;手动测试难免会出现疏漏和人为错误&#xff0c;因此…

K_A35_003 基于STM32等单片机采集矩阵按键模块值 串口与OLED0.96双显示

K_A35_003 基于STM32等单片机采集矩阵按键模块值 串口与OLED0.96双显示 所有资源导航一、资源说明二、基本参数引脚说明 三、驱动说明模块工作原理:对应程序: 四、部分代码说明1、接线引脚定义1.1、STC89C52RC矩阵按键模块1.2、STM32F103C8T6矩阵按键模块 五、基础知识学习与相…