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真正的类型为ThreadLocalMapThreadLocalMap本质就是一个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作用、原理以及问题相关推荐

  1. java.lang.ThreadLocal实现原理和源码分析

    java.lang.ThreadLocal实现原理和源码分析 1.ThreadLocal的原理:为每一个线程维护变量的副本.某个线程修改的只是自己的副本. 2.ThreadLocal是如何做到把变量变 ...

  2. 淘宝面试:说一下 ThreadLocal 的原理?网友:现在面试不看源码不行啊~

    作者 | 天才程序YUAN 来源 | https://urlify.cn/NRJvy2 前言 上周我侥幸通过美团一面(点击查看一面过程),岗位是java后端开发工程师.美团面试官给我进行了二面.面试过 ...

  3. 图解ThreadLocal核心原理

    线程安全问题的本质:对"共享资源","并发操作"导致的数据不一致问题.注意两个关键字:"共享资源","并发操作" 通常解 ...

  4. XAI之SHAP:SHAP算法(How—每个特征如何重要/解释单个样本的预测)的简介(背景/思想/作用/原理/核心技术点/优缺点)、常用工具库、应用案例之详细攻略

    XAI之SHAP:SHAP算法(How-每个特征如何重要/解释单个样本的预测)的简介(背景/思想/作用/原理/核心技术点/优缺点).常用工具库.应用案例之详细攻略 目录 SHAP的简介 0.SHAP算 ...

  5. java面试题(24)ThreadLocal的原理和使用场景

    一篇讲的很好的链接 1.四种引用类型   引用类型分为:强引用.软引用.弱引用.虚引用.   强引用:一般我们使用的都是强引用,特点是不会被gc回收,宁愿抛出内存溢出异常也不会回收:   软引用:特点 ...

  6. Java并发编程—ThreadLocal底层原理

    作者:Java3y 链接:https://www.zhihu.com/question/341005993/answer/793627819 来源:知乎 著作权归作者所有.商业转载请联系作者获得授权, ...

  7. 对ThreadLocal实现原理的一点思考

    前言 在<透彻理解Spring事务设计思想之手写实现>中,已经向大家揭示了Spring就是利用ThreadLocal来实现一个线程中的Connection是同一个,从而保证了事务.本篇博客 ...

  8. 快速搞懂ThreadLocal实现原理

    文章目录 一.ThreadLocal使用案例 二.ThreadLocal类的实现原理 2.1 核心方法set() 2.2 核心方法get() 2.3 核心方法remove() 三.ThreadLoca ...

  9. ThreadLocal工作原理和内存泄漏的预防

    ThreadLocal是什么? ThreadLocal是一个用于提供线程局部变量的一个工具类,用于保证线程安全,在他里面包含了一个ThreadLocalMap,真正的引用确是在Thread中,一般用p ...

最新文章

  1. MDA模型定义及扩展
  2. 终于可以光明正大的推别人了
  3. 最短路径算法----Dijkstra (转)
  4. 如何对Javascript代码进行二次压缩(混淆)
  5. OpenGait:首个步态识别框架开源了!
  6. 换个名字卖到海外?海外版小米9T与Redmi K20 Pro配置相差无几
  7. linux在线扩展文件系统空间ext2online
  8. linux系统q7文件,linux系统安装包的管理
  9. bc547可以用8050代换吗_怀孕可以用婴儿护肤品吗?
  10. 已解决(Python语法报错)SyntaxError invalid syntax
  11. 虚拟税务ukey托管服务器,税务Ukey托管来啦,给它一个家!企业开票更方便
  12. 截止失真放大电路_每周经典电路分析:一个经典实际运放的内部电路分析(1)
  13. 【算法专题】高精度之压位
  14. 老毛子的二级路由,通过无线中继方式设置与主路由在同一网段
  15. Python实现自由爆率抽奖小程序
  16. Maven中pom文件详解
  17. 深度分析蚂蚁金服RPC框架结构
  18. python执行javascript网页_Python 爬虫如何优雅地执行 javascript 函数
  19. 为什么计算机不显示桌面工具栏,电脑开机后发现桌面、任务栏不见了怎么解决?...
  20. GBase8s数据库GET DIAGNOSTICS 语句

热门文章

  1. async/await详解
  2. ehcache、memcache、redis三大缓存优缺点比较
  3. 战狼团之华为鸿蒙,华为鸿蒙目标一年跨过生死线
  4. HTTPS通信中的身份认证机制_网站劫持检测
  5. 组合游戏(分石子,nim游戏,sg)
  6. 仿脉脉PHP源码,如何实现类似脉脉网的二维人际关系
  7. 英雄传说6-特别攻略1
  8. 深圳区块链项目要如何进行包装?
  9. 以太网 载波侦听多路访问
  10. 初识 wireshark 查看qq数据流