【并发编程】ThreadLocal详解与原理

📫作者简介:小明Java问道之路,2022年度博客之星全国TOP3,专注于后端、中间件、计算机底层、架构设计演进与稳定性建设优化,文章内容兼具广度、深度、大厂技术方案,对待技术喜欢推理加验证,就职于知名金融公司后端高级工程师。

🏆 2022博客之星TOP3 | CSDN博客专家 | 后端领域优质创作者 | CSDN内容合伙人

🏆 InfoQ(极客邦)签约作者、阿里云专家 | 签约博主、51CTO专家 | TOP红人、华为云享专家

🔥如果此文还不错的话,还请👍关注、点赞、收藏三连支持👍一下博主~

本文目录

本文导读

一、ThreadLocal是什么

二、ThreadLocal的数据结构

三、ThreadLocal源码解析

1、 ThreadLocal的set()方法

2、 ThreadLocal的get()方法

3、 ThreadLocal的remove()方法

四、ThreadLocal使用场景

五、ThreadLocal内存泄露原因

六、如何正确的使用ThreadLocal

七、ThreadLocal为什么不将key设置为强引用

总结

本文导读

本文讲解ThreadLocal是什么、ThreadLocal的数据结构以及ThreadLocal源码set()/get()/remove()解析,ThreadLocal使用场景,如何正确的使用ThreadLocal,ThreadLocal内存泄露原因。

一、ThreadLocal是什么

ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。

ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。

ThreadLocal 提供了线程本地的实例。ThreadLocal 变量通常被private static修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。

ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景。

二、ThreadLocal的数据结构

ThreadLocal是作为当前线程中属性ThreadLocalMap集合中的某一个Entry的key值Entry(threadlocl,value),虽然不同的线程之间threadlocal这个key值是一样,但是不同的线程所拥有的ThreadLocalMap是独一无二的,也就是不同的线程间同一个ThreadLocal(key)对应存储的值(value)不一样,从而到达了线程间变量隔离的目的,但是在同一个线程中这个value变量地址是一样的。

1、每个Thread线程内部都有一个Map(ThreadLocalMap)

2、Map里面存储ThreadLocal对象(key)和线程的变量副本(value)

3、Thread内部的Map是由ThreadLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值。

4、对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离互不干扰。

三、ThreadLocal源码解析

1、 ThreadLocal的set()方法 ThreadLocal set赋值的时候首先会获取当前线程thread,获取thread线程中的ThreadLocalMap属性。如果map属性不为空,则直接更新value值,如果map为空,则实例化threadLocalMap,并将value值初始化。

scss 复制代码
public void set(T value) {
Thread t = Thread.currentThread(); // 1、获取当前线程

// 2、获取线程中的threadLocalMap ,如果threadLocalMap不为空直接更新要保存的变量值
// 否则创建threadLocalMap 并赋值
ThreadLocalMap map = getMap(t);
	if (map != null)
  map.set(this, value);
else
  createMap(t, value);  // 初始化thradLocalMap 并赋值
 }

ThreadLocalMap 是 ThreadLocal 的内部静态类,而它的构成主要是用Entry来保存数据 ,而且还是继承的弱引用。在Entry内部使用ThreadLocal作为key,使用我们设置的value作为value。

scala 复制代码
static class ThreadLocalMap {
  static class Entry extends WeakReference<ThreadLocal<?>> { // Java弱引用
  Object value;

  Entry(ThreadLocal<?> k, Object v) {
  	super(k);
  	value = v;
}
 }
 }

2、 ThreadLocal的get()方法

java 复制代码
public T get() {
Thread t = Thread.currentThread(); // 1、获取当前线程

ThreadLocalMap map = getMap(t);   // 2、获取当前线程的ThreadLocalMap

  if (map != null) { // 3、如果map数据不为空,获取threalLocalMap中存储的值
  ThreadLocalMap.Entry e = map.getEntry(this);
  if (e != null) {
  	@SuppressWarnings("unchecked")
  	T result = (T)e.value;
  	return result;
  	}
}

	// 如果是数据为null则初始化,TheralLocalMap中存放key为threadLocal值为null
return setInitialValue();
  }

3、 ThreadLocal的remove()方法

remove方法直接将ThrealLocal对应的值从当前相差Thread中的ThreadLocalMap中删除,

csharp 复制代码
public void remove() {
	ThreadLocalMap m = getMap(Thread.currentThread());
	if (m != null)
  m.remove(this);
  }

四、ThreadLocal使用场景

ThreadLocal 适用于如下两种场景:1、每个线程需要有自己单独的实例;2、实例需要在多个方法中共享,但不希望被多线程共享。

1、存储用户Session(不同线程获取到的用户信息不一样)

2、数据库连接,处理数据库事务

3、数据跨层传递

4、Spring使用ThreadLocal解决线程安全问题

五、ThreadLocal内存泄露原因

Entry将ThreadLocal作为Key,值作为value保存,它继承自WeakReference,注意构造函数里的第一行代码super(k),这意味着ThreadLocal对象是一个「弱引用」。

主要两个原因:1、没有手动删除这个Entry;2、CurrentThread 当前线程依然运行

解决方案:1、只要在使用完下ThreadLocal,调用remove方法删除对应的Entry,就能避免内存泄漏。

2、由于ThreadLocalMap 是 Thread 的一个属性,被当前线程所引用,所以ThreadLocalMap的生命周期跟 Thread 一样长。如果threadlocal变量被回收,那么当前线程的threadlocal 变量副本指向的就是key=null,也即entry(null,value),那这个entry对应的value永远无法访问到。如果ThreadLocal场景采用线程池,这样就可能导致非常多的entry(null,value)出现,从而导致内存泄露。

综上, ThreadLocal 内存泄漏的根源是:如果没有手动删除(remove()方法)对应 key 就会导致entry(null,value)的对象越来越多,从而导致内存泄漏。

六、如何正确的使用ThreadLocal

1、将ThreadLocal变量定义成private static的,这样的话ThreadLocal的生命周期就更长,由于一直存在ThreadLocal的强引用,所以ThreadLocal也就不会被回收,也就能保证任何时候都能根据ThreadLocal的弱引用访问到Entry的value值,然后remove它,防止内存泄露

2、每次使用完ThreadLocal,都调用它的remove()方法,清除数据。

七、ThreadLocal为什么不将key设置为强引用

如果key设计成强引用且没有手动remove(),ThreadLocal ref被回收了,但是因为threadLocalMap的Entry强引用了threadLocal(key就是threadLocal), 造成ThreadLocal无法被回收。

当前线程始终有强引用链CurrentThread Ref → CurrentThread →Map(ThreadLocalMap)-> entry,Entry就不会被回收( Entry中包括了ThreadLocal实例和value),导致Entry内存泄漏,也就是说ThreadLocalMap中的key使用了强引用是无法完全避免内存泄漏的

弱引用比强引用可以多一层保障弱引用的 ThreadLocal 会被回收,对应value在下一次 ThreadLocaI 调用 get()/set()/remove() 中的任一方法的时候会被清除,从而避免内存泄漏。

总结

本文讲解ThreadLocal是什么、ThreadLocal的数据结构以及ThreadLocal源码set()/get()/remove()解析,ThreadLocal使用场景,如何正确的使用ThreadLocal,ThreadLocal内存泄露原因。

相关推荐
猿饵块8 分钟前
cmake--get_filename_component
java·前端·c++
编程小白煎堆10 分钟前
C语言:枚举类型
java·开发语言
王哈哈嘻嘻噜噜16 分钟前
c语言中“函数指针”
java·c语言·数据结构
qq_3391911425 分钟前
spring boot admin集成,springboot2.x集成监控
java·前端·spring boot
GoFly开发者32 分钟前
GoFly快速开发框架/Go语言封装的图像相似性比较插件使用说明
开发语言·后端·golang
苹果酱056740 分钟前
通过springcloud gateway优雅的进行springcloud oauth2认证和权限控制
java·开发语言·spring boot·后端·中间件
Sunny_yiyi43 分钟前
Gateway--服务网关
java·开发语言·gateway
Mike!1 小时前
C++进阶 set和map讲解
java·开发语言·数据结构·c++·set·map·cpp
翔云1234561 小时前
Go语言的垃圾回收(GC)机制的迭代和优化历史
java·jvm·golang·gc
不见长安见晨雾2 小时前
将Java程序打包成EXE程序
java·开发语言