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当中用…

C语言从头学13——流程控制(二)

接着学习流程控制&#xff0c;并继续上一篇的编号。 4、while 语句 while语句用于循环结构&#xff0c;满足条件时&#xff0c;不断执行循环体&#xff0c;直到条件不满足时退出。如果 一直满足循环条件无法结束循环&#xff0c;会陷入死循环&#xff0c;这是应当避免的…

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默认的工具链中缺少一个工具…

使用AXI MIG/Proc Sys Reset

使用AXI MIG/Proc Sys Reset 重要&#xff01;仅当您的设计中包含AXI MIG时&#xff0c;才执行以下步骤。 AXI-MIG的连接接口 1.选择在/mig_7series_0/S_AXI上运行连接自动化。 2.选择/micblaze_0&#xff08;缓存&#xff09;或/micblaze _0&#xff08;Periph&#xff09;选项…

【Spring】AOP——通知(Advice)

1、通知&#xff08;Advice&#xff09; 1.1简介 在AOP中&#xff0c;通知&#xff08;Advice&#xff09;是切面&#xff08;Aspect&#xff09;中的一部分&#xff0c;用于定义在连接点&#xff08;Joinpoint&#xff09;处应该执行的操作。通知类型可以在AOP框架中配置和使…

在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; 可以满足我们日…

python监控windows服务器的进程和服务

需求&#xff1a;软件公司提供的软件服务在服务器上会莫名其妙的挂掉&#xff0c;他们采用的方法也只有重启服务器&#xff0c;重启时间太久了&#xff0c;所以想弄一个监测进程和服务的程序&#xff0c;监测到挂了就重启进程或者服务 import psutil import time import sub…

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

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

fast-lio2代码学习

主要是记录学习和使用过程&#xff0c;参照文章 FAST-LIO2代码解析(一)_fast lio2 源码解析-CSDN博客 Ubuntu 18.04使用Livox mid 360 测试 FAST_LIO - 知乎 5.30 把ros系统的框架写成博客。 5.31号开始学习fastlio系统&#xff0c;预计两周之内学习完成 两周之后购置一个…

【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) })}})}) …

AI界的“拼夕夕”登场,为上万张GPU寻找新使命

在AI领域&#xff0c;一个全新的竞争者已经悄然登场。 AI行业果真有着近乎颠覆性的魅力&#xff01; 此次事件之后&#xff0c;AI界也许会迎来新一轮的血雨腥风&#xff01; AI的潮流到底会怎样流转&#xff0c;天知道。 幻方量化&#xff0c;这家以量化投资闻名的公司&…