由于 ThreadLocal 支持范型,如 ThreadLocal< StringBuilder >,为表述方便,后文用 变量 代表 ThreadLocal 本身,而用 实例 代表具体类型(如 StringBuidler )的实例。
写这篇文章的一个原因在于,网上很多博客关于 ThreadLocal 的适用场景以及解决的问题,描述的并不清楚,甚至是错的。下面是常见的对于 ThreadLocal的介绍
ThreadLocal为解决多线程程序的并发问题提供了一种新的思路
ThreadLocal的目的是为了解决多线程访问资源时的共享问题
还有很多文章在对比 ThreadLocal 与 synchronize 的异同。既然是作比较,那应该是认为这两者解决相同或类似的问题。
上面的描述,问题在于,ThreadLocal 并不解决多线程 共享 变量的问题。既然变量不共享,那就更谈不上同步的问题。
ThreadLoal 变量,它的基本原理是,同一个 ThreadLocal 所包含的对象(对ThreadLocal< String >而言即为 String 类型变量),在不同的 Thread 中有不同的副本(实际是不同的实例,后文会详细阐述)。这里有几点需要注意
那 ThreadLocal 到底解决了什么问题,又适用于什么样的场景?
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).
Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).
核心意思是
ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。ThreadLocal 变量通常被
private static
修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。
总的来说,ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景。后文会通过实例详细阐述该观点。另外,该场景下,并非必须使用 ThreadLocal ,其它方式完全可以实现同样的效果,只是 ThreadLocal 使得实现更简洁。
既然每个访问 ThreadLocal 变量的线程都有自己的一个“本地”实例副本。一个可能的方案是 ThreadLocal 维护一个 Map,键是 Thread,值是它在该 Thread 内的实例。线程通过该 ThreadLocal 的 get() 方案获取实例时,只需要以线程为键,从 Map 中找出对应的实例即可。该方案如下图所示
该方案可满足上文提到的每个线程内一个独立备份的要求。每个新线程访问该 ThreadLocal 时,需要向 Map 中添加一个映射,而每个线程结束时,应该清除该映射。这里就有两个问题:
其中锁的问题,是 JDK 未采用该方案的一个原因。
上述方案中,出现锁的问题,原因在于多线程访问同一个 Map。如果该 Map 由 Thread 维护,从而使得每个 Thread 只访问自己的 Map,那就不存在多线程写的问题,也就不需要锁。该方案如下图所示。
该方案虽然没有锁的问题,但是由于每个线程访问某 ThreadLocal 变量后,都会在自己的 Map 内维护该 ThreadLocal 变量与具体实例的映射,如果不删除这些引用(映射),则这些 ThreadLocal 不能被回收,可能会造成内存泄漏。后文会介绍 JDK 如何解决该问题。
该方案中,Map 由 ThreadLocal 类的静态内部类 ThreadLocalMap 提供。该类的实例维护某个 ThreadLocal 与具体实例的映射。与 HashMap 不同的是,ThreadLocalMap 的每个 Entry 都是一个对 键 的弱引用,这一点从super(k)
可看出。另外,每个 Entry 都包含了一个对 值 的强引用。
使用弱引用的原因在于,当没有强引用指向 ThreadLocal 变量时,它可被回收,从而避免上文所述 ThreadLocal 不能被回收而造成的内存泄漏的问题。
但是,这里又可能出现另外一种内存泄漏的问题。ThreadLocalMap 维护 ThreadLocal 变量与具体实例的映射,当 ThreadLocal 变量被回收后,该映射的键变为 null,该 Entry 无法被移除。从而使得实例被该 Entry 引用而无法被回收造成内存泄漏。
注:Entry虽然是弱引用,但它是 ThreadLocal 类型的弱引用(也即上文所述它是对 键 的弱引用),而非具体实例的的弱引用,所以无法避免具体实例相关的内存泄漏。
读取实例方法如下所示
读取实例时,线程首先通过getMap(t)
方法获取自身的 ThreadLocalMap。从如下该方法的定义可见,该 ThreadLocalMap 的实例是 Thread 类的一个字段,即由 Thread 维护 ThreadLocal 对象与具体实例的映射,这一点与上文分析一致。
获取到 ThreadLocalMap 后,通过map.getEntry(this)
方法获取该 ThreadLocal 在当前线程的 ThreadLocalMap 中对应的 Entry。该方法中的 this 即当前访问的 ThreadLocal 对象。
如果获取到的 Entry 不为 null,从 Entry 中取出值即为所需访问的本线程对应的实例。如果获取到的 Entry 为 null,则通过setInitialValue()
方法设置该 ThreadLocal 变量在该线程中对应的具体实例的初始值。
设置初始值方法如下
该方法为 private 方法,无法被重载。
首先,通过initialValue()
方法获取初始值。该方法为 public 方法,且默认返回 null。所以典型用法中常常重载该方法。
然后拿到该线程对应的 ThreadLocalMap 对象,若该对象不为 null,则直接将该 ThreadLocal 对象与对应实例初始值的映射添加进该线程的 ThreadLocalMap中。若为 null,则先创建该 ThreadLocalMap 对象再将映射添加其中。
这里并不需要考虑 ThreadLocalMap 的线程安全问题。因为每个线程有且只有一个 ThreadLocalMap 对象,并且只有该线程自己可以访问它,其它线程不会访问该 ThreadLocalMap,也即该对象不会在多个线程中共享,也就不存在线程安全的问题。
除了通过initialValue()
方法设置实例的初始值,还可通过 set 方法设置线程内实例的值,如下所示。
该方法先获取该线程的 ThreadLocalMap 对象,然后直接将 ThreadLocal 对象(即代码中的 this)与目标实例的映射添加进 ThreadLocalMap 中。当然,如果映射已经存在,就直接覆盖。另外,如果获取到的 ThreadLocalMap 为 null,则先创建该 ThreadLocalMap 对象。
对于已经不再被使用且已被回收的 ThreadLocal 对象,它在每个线程内对应的实例由于被线程的 ThreadLocalMap 的 Entry 强引用,无法被回收,可能会造成内存泄漏。
针对该问题,ThreadLocalMap 的 set 方法中,通过 replaceStaleEntry 方法将所有键为 null 的 Entry 的值设置为 null,从而使得该值可被回收。另外,会在 rehash 方法中通过 expungeStaleEntry 方法将键和值为 null 的 Entry 设置为 null 从而使得该 Entry 可被回收。通过这种方式,ThreadLocal 可防止内存泄漏。
对于第一点,每个线程拥有自己实例,实现它的方式很多。例如可以在线程内部构建一个单独的实例。ThreadLoca 可以以非常方便的形式满足该需求。
对于第二点,可以在满足第一点(每个线程有自己的实例)的条件下,通过方法间引用传递的形式实现。ThreadLocal 使得代码耦合度更低,且实现更优雅。
对于 Java Web 应用而言,Session 保存了很多信息。很多时候需要通过 Session 获取信息,有些时候又需要修改 Session 的信息。一方面,需要保证每个线程有自己单独的 Session 实例。另一方面,由于很多地方都需要操作 Session,存在多方法共享 Session 的需求。如果不使用 ThreadLocal,可以在每个线程内构建一个 Session实例,并将该实例在多个方法间传递,如下所示。
该方法是可以实现需求的。但是每个需要使用 Session 的地方,都需要显式传递 Session 对象,方法间耦合度较高。
这里使用 ThreadLocal 重新实现该功能如下所示。
使用 ThreadLocal 改造后的代码,不再需要在各个方法间传递 Session 对象,并且也非常轻松的保证了每个线程拥有自己独立的实例。
如果单看其中某一点,替代方法很多。比如可通过在线程内创建局部变量可实现每个线程有自己的实例,使用静态变量可实现变量在方法间的共享。但如果要同时满足变量在线程间的隔离与方法间的共享,ThreadLocal再合适不过。
标题:《Best Practices for Data-Efficient Modeling in NLG:How to Train Production-Ready Neural Models with Less Data》作者:Facebook时间:2020项目地址:https://github.com/facebookresearch/DataEfficientNLG(只是个数据集仓库,暂时还没有开放code)中文:数据高效建模的最佳实践NLG:如何用较少的数据训练可落地的神经网络模型简介_bucket data训练策略
以程序中轴1为例,在轴不执行定位动作时轴1立即停止标志”M1302”一直处于导通的状态,在轴也没有执行JOG运行的状态下,轴1的状态应该是待机状态,轴1BUSY信号”X30”不导通(如果轴1BUSY信号导通则代表该轴正在运转中),如果PLC程序没有执行定位控制或JOG运转,轴1BUSY信号”X30”却导通了,这种情况就属于非正常启动,此时应该向轴1发出立即停止信号,即将轴1停止信号”U2\G30100.0”导通,使轴立即停止。执行原点回归动作,将定位启动的(16位)存储缓存区(轴1/ U*\G4300;_三菱qd77ms16
运行php的时候出现计算机中丢失 MSVCR110.dll怎么解决一、总结一句话总结:因为现在php所有的 5.5 环境都是基于 vc11 的编译脚本下生成的,所以在 windows 下你得安装相关组件。这里直接把MSVCR110.dll拷贝进C 盘的 syste32 文件夹不行,还是得安装vc11编译环境才行。vc11编译环境需要装32位,phpstudy的..._phpmsvcr10.dll
转载:https://blog.csdn.net/a6840231/article/details/82584886从网站http://www.fftw.org/install/windows.html上下载最新的预编译文件:64-bit version: fftw-3.3.5-dll64.zip,解压。点击电脑桌面左下角的“开始”→“所有程序”,找到“visual studio 201..._fftw3.h
51单片机按键控制流水灯
数据库主从同步配置是开发中比较常见的需求,在MySql里,主从同步主要有两种,bin-log和gtid。下面就主要总结一下使用gtid模式配置主从同步的过程。_mysql5.7 gtid主从配置
详细版搭建GitHub图床
win10系统下如何使用Registry断开映射的网络驱动器如果您不再需要访问映射的网络驱动器,请使用以下步骤在Windows 10上正确断开连接。在Windows 10上,映射驱动器的功能允许您快速访问存储在连接到网络的其他设备上的文件。但是,您可能需要删除已映射的网络驱动器,因为您不再需要它,网络中的设置正在更改,或断开连接并重新连接以解决映射问题。无论原因是什么,Windows 10都包含映..._断开连接的网络驱动器怎么删除
Java是一种强数据类型的语言(必须要声明类型)Java中的数据类型 分为: 简单数据类型和引用数据类型简单数据类型:java中有8中简单数据类型四种整形,一种字符型,一种布尔型,俩种浮点型int ,short,byte,long 都是整型数据double和float 浮点型char 字符型boolean 布尔型
数论方向的题目,最近开始做做这方面的简单题或者是模板题题目大意:根据费马定理,如果p是一个素数,则对任意数字a,有,现在,题目要求找出p[不是素数]时,满足此式的算式题目链接其中运用到了快速幂取模的操作(由于数字太过大),这大概也是第一次解决带有mod类型的题,虽然是个超级水题 = =这种快速幂取模的方法是纯模拟方法,网上其他代码,例如用&判断奇偶的代码质量比我这个好很..._poj3641
Mybitis与Spring集成
https://mp.weixin.qq.com/s/Bh-L3syzJSHhLPZqzHvIyA这篇文章给出了如何绘制中国人口密度图,但是运行存在一些问题,我在一些地方进行了修改。本人使用的IDE是anaconda,因此事先在anaconda prompt 中安装Basemap包conda install Basemap新建文档,导入需要的包import matplotlib.pyplot as..._python 人口热力图