ThreadLocal作用、原理以及问题
ThreadLocal
1、ThreadLocal的作用
在多线程访问共享资源时会采取一定的线程同步方式(如:加锁)来解决带来的并发问题。(如图)
使用ThreadLocal对共享资源的访问也可以解决并发问题
作用:ThreadLocal提供了线程的本地变量,即当创建一个变量后,每个线程对其进行访问的时候访问的是自己线程的变量。
这里的本地内存并不是线程的工作内存,而是Thread类中的一个变量,而不是放在不是存放在ThreadLocal实例里面
这样做的好处:
- 线程安全,可以避免多线程访问同一个共享变量导致的并发问题。
- 不需要加锁,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。
2、 ThreadLocal的原理
2.1 ThreadLocalMap
前面提到:每个线程的本地变量是放在调用线程Thread类中的一个变量threadLocals
中,而不是放在不是存放在ThreadLocal实例里面
public class Thread implements Runnable {..../* ThreadLocal values pertaining to this thread. This map is maintained* by the ThreadLocal class. */ThreadLocal.ThreadLocalMap threadLocals = null; // 存放本地变量,默认为null....
}
可以看到threadLocals
真正的类型为ThreadLocalMap
,ThreadLocalMap
本质就是一个HashMap,当创建一个ThreadLocal对象时就会将此对象添加到map中。其中key就是当前ThreadLocal的实例对象引用(注意:这里的引用为弱引用),value则是用set
方法设定的值。
2.1.1 为什么要用map?
这是因为在实际使用中可能会有多个ThreadLocal变量,因此需要将这些ThreadLocal添加到map中。
2.1.2 为什么ThreadLocalMap中把ThreadLocal对象存储为Key时使用的是弱引用
类型 | 回收时间 | 应用场景 |
---|---|---|
强引用 | 一直存活,除非GC Roots不可达 | 所有程序的场景,基本对象,自定义对象等 |
软引用 | 内存不足时会被回收 | 一般用在对内存非常敏感的资源上,用作缓存的场景比较多,例如:网页缓存、图片缓存 |
弱引用 | 只能存活到下一次GC前 | 生命周期很短的对象,例如ThreadLocal中的Key。 |
虚引用 | 随时会被回收, 创建了可能很快就会被回收 | 可能被JVM团队内部用来跟踪JVM的垃圾回收活动 |
- 一来说使用ThreadLocal时会有两个引用指向ThreadLocal对象,一个是通过
new
创建ThreadLocal对象的强引用
,一个是ThreadLocalMap对ThreadLocal对象的弱引用。 - 如果为强引用: 由于ThreadLocalMap是属于线程的,而我们创建多线程时一般是使用线程池进行创建,线程池中的部分线程在任务结束后是不会关闭的,那么这部分线程中的ThreadLocalMap将会一直持有对ThreadLocal对象的强引用,导致ThreadLocal对象无法被垃圾回收,从而造成内存泄漏。
- 因此设置为弱引用:在下一次垃圾回收时,无论内存空间是否足够,只有弱引用指向的对象都会被直接回收。所以将ThreadLocalMap对ThreadLocal对象的引用设置成弱引用,就能避免ThreadLocal对象无法回收导致内存泄漏的问题。
2.2 源码分析
我们从源码中了解ThreadLocal的原理,下面来看一下具体ThreadLocal是如何实现的。
2.2.1 set
public void set(T value) {// 获取当前线程Thread t = Thread.currentThread(); // 获取当前线程中的threadLocalsThreadLocalMap map = getMap(t); if (map != null) {// 调用map的set方法map.set(this, value); } else {// 如果map为空则创建mapcreateMap(t, value); }}
其中getMap方法如下:
ThreadLocalMap getMap(Thread t) {return t.threadLocals;
}
可以看出,set方法首先会获取当前的线程,然后会通过当前线程取出前面所说的类型为ThreadLocalMap的threadLocals
变量,通过此变量将值存在当前的Thread中。由于threadLocals
的默认值为空,所以当第一次调用set方法时会调用createMap方法。
createMap方法:其实就是new了一个ThreadLocalMap对象
void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);}
2.2.2 get
public T get() {// 获取当前线程Thread t = Thread.currentThread(); // 获取当前线程中的threadLocalsThreadLocalMap map = getMap(t);if (map != null) {// 如果不为空则返回当前本地变量的值 ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}// 如果threadLocals为空或者threadLocals对应的值为空,则初始化return setInitialValue();
}
可以看出,get方法主要就是获取当前线程中的threadLocals,如果不为空则取出对应的值,否则调用初始化方法setInitialValue
setInitialValue方法:
private T setInitialValue() {// 初始化值T value = initialValue();Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {map.set(this, value);} else {createMap(t, value);}// ThreadLocal的又一个扩展。该ThreadLocal关联的值在线程结束前会被特殊处理,处理方式取决于回调方法threadTerminated(T value)if (this instanceof TerminatingThreadLocal) {TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);}return value;
}
如果当前线程的threadLocals变量不为空,则设置当前线程的本地变量值为null,否则调用createMap方法
2.2.3 remove
public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());if (m != null) {// 从map中删除m.remove(this);}
}
每个线程的本地变量存放在线程自己的threadLocals中,如果当前线程一直不消亡,那么这些本地变量会一直存在,所以可能会造成内存溢出,因此使用完毕后记得调用remove方法删除对应线程的threadLocals重的本地变量。
3、ThreadLocal内存泄漏问题
在ThreadLocalMap中是使用Entry类型的数组实现的 ,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。
3.1 存在的问题
前面讲到,使用ThreadLocal的弱引用的主要原因是防止ThreadLocalMap一直持有对ThreadLocal对象的强引用,导致ThreadLocal对象无法被垃圾回收,从而造成内存泄漏。设置为弱引用,当前线程的ThreadLocalMap里面的ThreadLocal变量的弱引用会在gc时被回收。但是对应的value的引用是强引用,因此还是会造成内存泄漏,这个时候ThreadLocalMap里面会存在key为null,但是value不为null的选项。所以ThreadLocal类定义了expungeStaleEntry
方法用于清理key为null的value。expungeStaleEntry在remove中方法中调用。
private void remove(ThreadLocal<?> key) {Entry[] tab = table;// 计算当前ThreadLocal变量锁在的table数组位置,快速定位法int len = tab.length;int i = key.threadLocalHashCode & (len-1);// 防止快速定位法失效,遍历table数组for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {if (e.get() == key) {// 调用WeakReference的clear方法清除弱引用e.clear();// 清除key为null的元素expungeStaleEntry(i);return;}}}
expungeStaleEntry方法
private int expungeStaleEntry(int staleSlot) {Entry[] tab = table;int len = tab.length;// expunge entry at staleSlottab[staleSlot].value = null;tab[staleSlot] = null;size--;// Rehash until we encounter nullEntry e;int i;for (i = nextIndex(staleSlot, len);(e = tab[i]) != null;i = nextIndex(i, len)) {ThreadLocal<?> k = e.get();// 清除key为null的value引用if (k == null) {e.value = null;tab[i] = null;size--;} else {int h = k.threadLocalHashCode & (len - 1);if (h != i) {tab[i] = null;// Unlike Knuth 6.4 Algorithm R, we must scan until// null because multiple entries could have been stale.while (tab[h] != null)h = nextIndex(h, len);tab[h] = e;}}}return i;}
可以看出,在expungeStaleEntry方法中会从当前位置开始遍历table,并清理key为null的元素。
注意:虽然ThreadLocal有expungeStaleEntry方法清除key为null的元素。但是可以看出循环退出的条件为遇到null的元素,因此null之后的并且key为null的元素无法被清除。并且这种清除方式是不及时的。所以在一定情况下依然会发生内存泄漏,最好的办法就是每次调用完之后及时使用remove方法。
ThreadLocal作用、原理以及问题相关推荐
- java.lang.ThreadLocal实现原理和源码分析
java.lang.ThreadLocal实现原理和源码分析 1.ThreadLocal的原理:为每一个线程维护变量的副本.某个线程修改的只是自己的副本. 2.ThreadLocal是如何做到把变量变 ...
- 淘宝面试:说一下 ThreadLocal 的原理?网友:现在面试不看源码不行啊~
作者 | 天才程序YUAN 来源 | https://urlify.cn/NRJvy2 前言 上周我侥幸通过美团一面(点击查看一面过程),岗位是java后端开发工程师.美团面试官给我进行了二面.面试过 ...
- 图解ThreadLocal核心原理
线程安全问题的本质:对"共享资源","并发操作"导致的数据不一致问题.注意两个关键字:"共享资源","并发操作" 通常解 ...
- XAI之SHAP:SHAP算法(How—每个特征如何重要/解释单个样本的预测)的简介(背景/思想/作用/原理/核心技术点/优缺点)、常用工具库、应用案例之详细攻略
XAI之SHAP:SHAP算法(How-每个特征如何重要/解释单个样本的预测)的简介(背景/思想/作用/原理/核心技术点/优缺点).常用工具库.应用案例之详细攻略 目录 SHAP的简介 0.SHAP算 ...
- java面试题(24)ThreadLocal的原理和使用场景
一篇讲的很好的链接 1.四种引用类型 引用类型分为:强引用.软引用.弱引用.虚引用. 强引用:一般我们使用的都是强引用,特点是不会被gc回收,宁愿抛出内存溢出异常也不会回收: 软引用:特点 ...
- Java并发编程—ThreadLocal底层原理
作者:Java3y 链接:https://www.zhihu.com/question/341005993/answer/793627819 来源:知乎 著作权归作者所有.商业转载请联系作者获得授权, ...
- 对ThreadLocal实现原理的一点思考
前言 在<透彻理解Spring事务设计思想之手写实现>中,已经向大家揭示了Spring就是利用ThreadLocal来实现一个线程中的Connection是同一个,从而保证了事务.本篇博客 ...
- 快速搞懂ThreadLocal实现原理
文章目录 一.ThreadLocal使用案例 二.ThreadLocal类的实现原理 2.1 核心方法set() 2.2 核心方法get() 2.3 核心方法remove() 三.ThreadLoca ...
- ThreadLocal工作原理和内存泄漏的预防
ThreadLocal是什么? ThreadLocal是一个用于提供线程局部变量的一个工具类,用于保证线程安全,在他里面包含了一个ThreadLocalMap,真正的引用确是在Thread中,一般用p ...
最新文章
- MDA模型定义及扩展
- 终于可以光明正大的推别人了
- 最短路径算法----Dijkstra (转)
- 如何对Javascript代码进行二次压缩(混淆)
- OpenGait:首个步态识别框架开源了!
- 换个名字卖到海外?海外版小米9T与Redmi K20 Pro配置相差无几
- linux在线扩展文件系统空间ext2online
- linux系统q7文件,linux系统安装包的管理
- bc547可以用8050代换吗_怀孕可以用婴儿护肤品吗?
- 已解决(Python语法报错)SyntaxError invalid syntax
- 虚拟税务ukey托管服务器,税务Ukey托管来啦,给它一个家!企业开票更方便
- 截止失真放大电路_每周经典电路分析:一个经典实际运放的内部电路分析(1)
- 【算法专题】高精度之压位
- 老毛子的二级路由,通过无线中继方式设置与主路由在同一网段
- Python实现自由爆率抽奖小程序
- Maven中pom文件详解
- 深度分析蚂蚁金服RPC框架结构
- python执行javascript网页_Python 爬虫如何优雅地执行 javascript 函数
- 为什么计算机不显示桌面工具栏,电脑开机后发现桌面、任务栏不见了怎么解决?...
- GBase8s数据库GET DIAGNOSTICS 语句