ThreadLocal:熟悉的陌生词,你应该要知道的。

Hi,大家好,我是抢老婆酸奶的小肥仔。

在很多的地方,我们都能看到ThreadLocal的身影,也会用到它,但是我们真的就了解它吗?

今天我们来叨叨这个我们既熟悉又陌生的小伙伴,废话不多说开整。

1、啥是ThreadLocal

一言以蔽之:线程各行其是,即线程间的隔离性。

在多线程时,访问同一个共享变量,可能会存在线程安全问题,为了线程安全,我们会为这个变量加锁,以达到同一时间只能有一个线程进行访问,其他线程只能等待。这样就会导致程序复杂性,开发人员也必须对锁的使用特别熟练,否则可能产生死锁。

ThreadLocal可以将创建的变量作为当前线程私有变量

我们通过代码来看看ThreadLocal是否是只操作线程本身的私有变量的。

/*** @author: jiangjs* @description: 使用ThreadLocal* @date: 2023/7/28 14:55**/
public class UseThreadLocal {private static final ThreadLocal<String> tl = new ThreadLocal<>();public static void main(String[] args) {new Thread(() -> {tl.set("线程A名称【" + Thread.currentThread().getName() + "】");System.out.println("获取当前线程A的名称:" + tl.get());tl.remove();System.out.println("验证是否删除当前线程A的名称:" + tl.get());}).start();new Thread(() -> {tl.set("线程B名称【" + Thread.currentThread().getName()+"】");System.out.println("获取当前线程B的名称:" + tl.get());System.out.println("验证是否删除当前线程B的名称:" + tl.get());}).start();}
}

执行结果:

上述代码中,我们通过创建两个线程,分别在定义的ThreadLocal中添加了各自的线程名称,但是在线程A中调用了ThreadLocal提供的remove方法进行了删除。我们发现线程A的线程名称被删除了,而线程B并未受影响,因此,ThreadLocal做到了隔离性,线程间的变量互不干扰。

2、ThreadLoal原理

我们翻开ThreadLocal的源码,会发现其内部有一个ThreadLocalMap的内部静态类。根据这个静态内部类上的注释我们可以了解,这是一个定制的散列映射,只适合于维护线程本地值。

在ThreadLocal简介里面我们用到了三个方法:set(T value) ,get(),remove()三个方法,我们来看看他们的源码,通过他们的源码来了解ThreadLocal的原理。

2.1 set(T value)

set(T value)方法源码:

public void set(T value) {//获取当前线程Thread t = Thread.currentThread();//根据当前线程,获取ThreadLocalMap对象ThreadLocalMap map = getMap(t);if (map != null)//ThreadLocalMap对象不为空,则直接赋值map.set(this, value);else//ThreadLocalMap对象为空,则创建对象,赋值createMap(t, value);
}

上述源码中我们知道,ThreadLocalMap则是以当前线程作为key,而传递的value则是ThreadLocalMap保存的值。

在上述源码中,我们用到两个内部方法:getMap(t), createMap(t, value),我们也顺便看看这两个方法的源码。

getMap(t)源码:

ThreadLocalMap getMap(Thread t) {return t.threadLocals;
}

源码返回的是当前线程中的threadLocals变量:ThreadLocal.ThreadLocalMap threadLocals = null; 因此,当我们第一次调用时,返回的就是null。

createMap(t, value)源码:

void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);
}

在createMap()中,则是直接调用ThreadLocalMap的构造方法创建对象,并赋值给线程的threadLocals变量。

上述的源码比较好理解,也就是获取当前线程,通过当前线程获取自身的成员变量threadLocals,而threadLocals其实就是TheadLocal的ThreadLocalMap对象,如果对象不为null,则直接调用set方法赋值,否则创建ThreadLocalMap对象后并实例化当前线程的threadLocals成员变量。

2.2 get()

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

上述源码,获取当前线程的ThreadLocalMap对象即threadLocals成员变量,如果为空,则调用setInitialValue()方法初始化threadLocals成员变量的值,否则直接返回绑定的本地变量。

private T setInitialValue() {T value = initialValue();Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);return value;
}

initialValue() :返回的是一个null值。因此如果get()获取不到值时,则直接返回的就是null,setInitialValue()的方法也是调用createMap(t, value)创建ThreadLocalMap,只不过传递的值为null。

2.3 remove()

remove()方法源码:

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

上述源码就比较简单,获取当前线程的ThreadLocalMap对象,如果不为空,则删除value值。

2.4 关于ThreadLocalMap

在上述的ThreadLocal的三个方法中,其本质都是操作ThreadLocalMap,我们通过createMap中初始化ThreadLocalMap时调用的构造方法来看看其本质:

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {table = new Entry[INITIAL_CAPACITY];int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);table[i] = new Entry(firstKey, firstValue);size = 1;setThreshold(INITIAL_CAPACITY);
}

INITIAL_CAPACITY:定义Entry数组的长度,值:16。

threadLocalHashCode:获取当前线程的hash值。

setThreshold(INITIAL_CAPACITY) :计算扩容因子。

Entry源码:

static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}
}

从源码中,我们可以看出Entry继承了WeakReference,ThreadLocal作为key是一个弱引用,而弱引用在JVM的每一次GC时都会被回收。

3、ThreadLocal一些特性

3.1 变量不具有传递性

简单来说:同一个ThreadLocal在父线程中设置值后,子线程也是无法获取的。

其实不难理解,毕竟ThreadLocal主打的就是一个变量的隔离性。

我们也用代码来验证一下。

public class UseThreadLocal {private static final ThreadLocal<String> tl = new ThreadLocal<>();public static void main(String[] args) {tl.set("获取的值");new Thread(() -> {System.out.println("获取主线线程的值:" + tl.get());}).start();}
}

执行结果:

如果想要子线程获取父线程的值则可以使用:InheritableThreadLocal。

3.2 关于OOM

3.2.1 原因

在ThreadLocalMap中,其定义的Entity是继承了WeakReference,并指定ThreadLocal<?> k做为key,且是一个弱引用,当ThreadLocal作为key在没有外部强引用时,就会被GC回收,而value作为强引用不会被回收,就会造成存在key为null,value不为null的Entity,此时若线程一直不结束,或线程作为线程池中的一员,即使结束也不会被销毁,这样久而久之就可能造成OOM。

3.2.2 如何避免OOM呢?

1、使用完ThreadLocal后,调用remove()方法清除数据

2、将ThreadLocal变量使用private static,使其一直存在强引用,同时能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉 。

4、总结

ThreadLocal采用了多线程隔离机制,在多线程下线程将共享变量复制一个副本,线程各自只能使用线程本身的副本变量,为提供了访问变量的安全性。同时采用了空间换时间的思想,通过ThreadLocalMap来管理线程成员变量信息。

好了,今天就跟大家叨叨到这,谢谢大家。

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

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

相关文章

云原生架构内涵_3.主要架构模式

云原生架构有非常多的架构模式&#xff0c;这里列举一些对应用收益更大的主要架构模式&#xff0c;如服务化架构模式、Mesh化架构模式、Serverless模式、存储计算分离模式、分布式事务模式、可观测架构、事件驱动架构等。 1.服务化架构模式 服务化架构是云时代构建云原生应用的…

[ C++ ] 深入理解模板( 初 阶 )

函数模板 函数模板格式 template <typename T1, typename T2,......,typename Tn> 返回值类型 函数名(参数列表){} 注意&#xff1a; typename是用来定义模板参数关键字&#xff0c;也可以使用class(切记&#xff1a;不能使用struct代替class) 函数模板的实例化 模板参数…

鸿蒙开发接口图形图像:【WebGL】

WebGL WebGL提供图形绘制的能力&#xff0c;包括对当前绘制图形的位置、颜色等进行处理。 WebGL标准图形API&#xff0c;对应OpenGL ES 2.0特性集。 说明&#xff1a; 开发前请熟悉鸿蒙开发指导文档&#xff1a; gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md…

c++编程(15)——list的模拟实现

欢迎来到博主的专栏——c编程 博主ID&#xff1a;代码小豪 文章目录 前言list的数据结构list的默认构造尾插与尾删iterator插入和删除构造、析构、赋值copy构造initializer_list构造operator 析构函数 前言 受限于博主当前的技术水平&#xff0c;暂时还不能模拟实现出STL当中用…

E. Binary Deque[双指针好思维题]

Binary Deque 题面翻译 有多组数据。 每组数据给出 n n n 个数&#xff0c;每个数为 0 0 0 或 1 1 1 。你可以选择从两边删数&#xff0c;求至少删几个数才可以使剩下的数总和为 s s s 。 如果不能达到 s s s &#xff0c;则输出 − 1 -1 −1 。 题目描述 Slavic h…

ABAP 在增强中COMMIT

前言 呃&#xff0c;又是很磨人的需求&#xff0c;正常情况下是不允许在增强中COMMIT的&#xff0c;会影响源程序本身的逻辑&#xff0c;但是这个需求就得这么干… 就是在交货单增强里面要再调用一次交货单BAPI&#xff0c;通过SO的交货单自动创建STO的交货单&#xff0c;如果…

pod install 报错 ‘SDK does not contain ‘libarclite‘ at the path...‘

报错内容&#xff1a; SDK does not contain ‘libarclite’ at the path ‘/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/arc/libarclite_iphoneos.a’; 这是报错已经很明确告诉我们&#xff0c;Xcode默认的工具链中缺少一个工具…

在Android中解析XML文件并在RecyclerView中显示

1. 引言 最近工作有解析外部xml文件在App中显示的需求&#xff0c;特来写篇文章记录一下&#xff0c;方便下次使用。 2. 准备工作 首先&#xff0c;在项目的AndroidManifest.xml文件中添加读取外部存储的权限声明。 <uses-permission android:name"android.permiss…

Java程序设计

一 Java基础知识 1 Java语言概述 1.1 发展历史 1.2 Java应用领域 Web开发&#xff1a;电子商务网站、内部管理系统、社交网络、门户网站移动开发&#xff1a;Android开发桌面开发&#xff1a;办公软件、游戏、工具软件企业应用开发&#xff1a;客户关系管理、企业资源计划、…

3DEXPERIENCE DELMIA Role: RVN - Robotics Virtual Commissioning Analyst

Discipline: Robotics Role: RVN - Robotics Virtual Commissioning Analyst 通过准确地模拟连接到PLC程序的机器人、设备和传感器&#xff0c;在制造虚拟孪生上执行虚拟调试情景 为任何机器人角色的多周期情景创建传感器&#xff0c;生成和变换零件启用 PLC 程序的虚拟验证和…

visio中画乘法器加法器符号

情景&#xff1a;在进行rtl设计时&#xff0c;需要画出简单的电路实现图&#xff0c;需要用到加法器&#xff0c;乘法器的符号。 位置&#xff1a;在visio的更多形状中&#xff0c;找到如下图的位置&#xff0c;进行添加&#xff0c;即可 最终效果&#xff1a; 可以满足我们日…

0基础认识C语言(理论+实操 2)

小伙伴们大家好&#xff0c;今天也要撸起袖子加油干&#xff01;万事开头难&#xff0c;越学到后面越轻松~ 话不多说&#xff0c;开始正题~ 前提回顾&#xff1a; 接上次博客&#xff0c;我们学到了转义字符&#xff0c;最后留下两个转义字符不知道大家有没有动手尝试了一遍&a…

【5.基础知识和程序编译及调试】

一、GCC概述&#xff1a;是GUN推出的多平台编译器&#xff0c;可将C/C源程序编译成可执行文件。编译流程分为以下四个步骤&#xff1a; 1、预处理 2、编译 3、汇编 4、链接 注&#xff1a;编译器根据程序的扩展名来分辨编写源程序所用的语言。根据不同的后缀名对他们进行相…

鸿蒙时间滑动选择器弹窗

例子&#xff1a; Button(打开弹窗).fontSize(14).width(106).height(32).padding({ left: 0, right: 0 }).fontColor(#999).onClick(()>{DatePickerDialog.show({selected:new Date(),onDateAccept:(value)>{AlertDialog.show({ message:JSON.stringify(value) })}})}) …

react使用react-quill富文本编辑器自定义上传图片,添加handlers后编辑器不显示问题

Quill介绍 Quill 是一款 API 驱动、功能强大的现代富文本编辑器。它具有易于拓展、各平台表现一致性等优点。Quill 官方 1.0 版本于 2016 年 9 月发布&#xff0c;目前在 Github 上有41.8k Star。 官网地址&#xff1a;Quill - Your powerful rich text editor github仓库地…

增强团队建设和创造力的 6 个敏捷游戏

加入敏捷框架提供了对资源的访问和支持&#xff0c;可以帮助你的组织最大限度地发挥敏捷的优势。它还提供了一个与其他敏捷从业者联系的平台&#xff0c;以共享最佳实践并相互学习。 实践敏捷工作方法可以让团队按照自己的节奏&#xff0c;尽可能多地发挥创造力来追求目标&…

自动驾驶路径决策算法——动态规划

文章内容来自b站up主忠厚老实的老王&#xff0c;视频链接如下&#xff1a; 自动驾驶决策规划算法第二章第二节(中) 参考线算法_哔哩哔哩_bilibili 其中host是自车位置&#xff0c;以host在参考线的投影为坐标原点&#xff0c;建立frenet坐标&#xff0c;此时host的坐标是(0,L0…

企业如何打造通证经济生态闭环详解(下)

一、原始账户&#xff1a;用户注册即生成【原始账户】【托管账户】。 原始账户用于存储用户所获取的通证积分&#xff0c;原始账户的公钥与私钥由用户所有&#xff0c;安全、私密、去中心化。 通过原始账户&#xff0c;用户可进行转账、收款的点对点传输&#xff0c;并可查看…

Vue——事件修饰符

文章目录 前言阻止默认事件 prevent阻止事件冒泡 stop 前言 在官方文档中对于事件修饰符有一个很好的说明&#xff0c;本篇文章主要记录验证测试的案例。 官方文档 事件修饰符 阻止默认事件 prevent 在js原生的语言中&#xff0c;可以根据标签本身的事件对象进行阻止默认事件…

如何编写高效的单片机代码?

单片机的程序比软开少一些&#xff0c;真正想编写出高效的代码&#xff0c;还是要积累很多年的。 在做研发工程师的10年里&#xff0c;我经历过几个公司&#xff0c;看过很多工程师写的代码&#xff0c;但真正能让我跪着看完的&#xff0c;极少。哪怕是大厂工程师&#xff0c;也…