当前位置:网站首页>ThreadLocal内存泄漏问题
ThreadLocal内存泄漏问题
2022-06-24 19:35:00 【Nice2cu_Code】
ThreadLocal原理
阅读本文章之前,需要先了解Java中强软弱虚的概念,传送地址:Java中强软弱虚四种引用
文章目录
一、介绍
- 可以解决多线程的数据安全问题,将当前线程关联一个数据(可以是普通变量,可以是对象,也可以是数组,集合等)
- 每个线程只能存取本线程的数据,无法操作其他线程的数据,各个线程的数据互相独立
- ThreadLocal中定义了
set、get、remove
方法,用来关联 / 取出 / 移除数据 - 每一个 ThreadLocal 对象,只能为当前线程关联一个数据,如果要为当前线程关联多个数据,就需要使用多个ThreadLocal 对象实例
二、快速入门
public class ThreadLocalTest {
public static void main(String[] args) throws InterruptedException {
//创建ThreadLocal对象实例
final ThreadLocal<Integer> th = new ThreadLocal<Integer>();
//t1线程
new Thread(new Runnable() {
@Override
public void run() {
try {
th.set(100); //t1线程存放数据
System.out.println("t1线程赋予的值:" + th.get());
Thread.sleep(2000); //t1线程睡眠2秒,睡眠的时间段内t2线程会运行
System.out.println("t1线程获取的值:"+th.get());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
Thread.sleep(1000);
//t2线程
new Thread(new Runnable() {
@Override
public void run() {
Integer ele = th.get(); //t2线程取出数据(此时为null,与t1无关系)
System.out.println("t2线程获取的值:" + ele);
th.set(200); //t2线程赋值
System.out.println("t2线程赋值后获取的值:"+th.get()); //t2线程从自己的副本中获取值
}
}).start();
}
}
运行结果:

三、ThreadLocal与synchronized的区别
- 原理不同
- synchronized
- 只提供了一份数据,让不同的线程竞争锁访问
- ThreadLocal
- 为每一个线程都提供了各自的数据,从而实现同时访问互不干扰
- synchronized
- 侧重点不同
- synchronized
- 多个线程之间访问资源的同步
- ThreadLocal
- 多线程中让每个线程之间的数据相互隔离
- synchronized
四、ThreadLocal内部结构
每个
Thread
维护一个ThreadLocalMap
,这个Map的key
是ThreadLocal
实例本身,value
是真正要存储的值图示
Map由Entry键值对组成
当
Thread
销毁之后,对应的ThreadLocalMap
也会随之销毁,能减少内存的使用
五、ThreadLocal核心方法源码
1. set方法
源码和对应的中文注释
/** * 参数是Map中的value值 */ public void set(T value) { // 获取当前线程对象 Thread t = Thread.currentThread(); // 获取此线程对象中维护的ThreadLocalMap对象 ThreadLocalMap map = getMap(t); // 判断map是否存在 if (map != null) // 存在则调用set方法,this表示此线程中TheadLocal实例 // 创建Entry并赋值 map.set(this, value); else // 如果当前线程不存在ThreadLocalMap对象,则创建Map createMap(t, value); } /** * 被调用的getMap方法 * @param 当前线程 * @return 对应维护的ThreadLocalMap */ ThreadLocalMap getMap(Thread t) { return t.threadLocals; } /** * 被调用的createMap方法 * @param 当前线程 * @param 存放到Map中的第一个entry的值 */ void createMap(Thread t, T firstValue) { //this是ThreadLocal实例 t.threadLocals = new ThreadLocalMap(this, firstValue); }
代码执行流程
- 首先获取当前线程,并根据当前线程获取一个Map
- 如果获取的Map不为空,则将参数设置到Map中(当前ThreadLocal的引用作为key),创建Entry并赋值
- 如果Map为空,则给该线程创建Map,并设置初始值,初始值即为参数值
2. get方法
源码和对应的中文注释
/** * 返回当前线程的Map中的value值 */ public T get() { // 获取当前线程对象 Thread t = Thread.currentThread(); // 获取此线程对象中维护的ThreadLocalMap对象 ThreadLocalMap map = getMap(t); // 如果此map存在 if (map != null) { // 以当前的ThreadLocal为key,调用getEntry获取对应的Entry ThreadLocalMap.Entry e = map.getEntry(this); // 如果Entry不为空,返回对应的value值 if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } //有两种情况执行setInitialValue()方法 //第一种情况: map不存在,表示此线程没有维护ThreadLocalMap对象 //第二种情况: map存在, 但是没有与当前ThreadLocal关联的entry return setInitialValue(); } /** * 被调用的setInitialValue方法,创建Entry并返回对应的value值,默认为null */ private T setInitialValue() { // 调用initialValue获取初始化的值 // 此方法可以被子类重写, 如果不重写默认返回null T value = initialValue(); // 获取当前线程对象 Thread t = Thread.currentThread(); // 获取此线程对象中维护的ThreadLocalMap对象 ThreadLocalMap map = getMap(t); // 判断map是否存在 if (map != null) // 存在则调用map.set设置此实体entry map.set(this, value); else // 1.当前线程Thread不存在ThreadLocalMap对象 // 2.则调用createMap进行ThreadLocalMap对象的初始化 // 3.并将t(当前线程)和value(默认为null)作为第一个entry存放至ThreadLocalMap中 createMap(t, value); // 返回设置的值value,默认为null return value; }
代码执行流程
- 首先获取当前线程, 根据当前线程获取一个Map
- 如果获取的 Map 不为空,则在Map中以 ThreadLocal 的引用作为key在Map中获取对应的
Entry e
- 如果 e 不为空,则返回
e.value
- 如果Map为空或者e为空,则通过
initialValue()
函数获取初始值 value(默认为null),然后用ThreadLocal的引用和value作为 firstKey 和 firstValue 创建一个新的 Map
总结: 先获取当前线程的 ThreadLocalMap 变量,如果存在则返回值,不存在则创建并返回初始值。
六、弱引用的使用
Entry对应的源码如下:
//Entry继承自一个弱引用
static class Entry extends WeakReference<ThreadLocal<?>> {
//属性value
Object value;
//构造器
Entry(ThreadLocal<?> k, Object v) {
super(k); //父类会创建一个弱引用,参数是ThreadLocal对象
value = v;
}
}
//被调用的父类方法
public WeakReference(T referent) {
super(referent);
}
//被调用的父类方法,第二个参数引用队列传递null值
Reference(T referent, ReferenceQueue<? super T> queue) {
this.referent = referent;
this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}
通过上述源码得知,创建Entry的时候会创建一个弱引用,并指向当前线程的ThreadLocal对象,如下图所示:

1. 为什么要使用弱引用?
如果Entry中的key使用强引用指向ThreadLocal对象,当ThreadLocal实例 threadLocal = null;
时,由于ThreadLocal对象仍然被Entry中的key强引用,所以ThreadLocal对象不会被垃圾回收,存在内存泄漏。
内存泄漏:对象不会被GC所回收,然而它却占用内存。
如果使用弱引用,当垃圾回收时,ThreadLocal对象被回收,可以解决ThreadLocal对象内存泄漏的问题。
2. 使用弱引用后是否依然存在内存泄漏?
当ThreadLocal对象被回收后,Entry中的key为null,无法获取value的值,无法回收,所以value指向的内存空间仍然存在内存泄漏问题,故应该使用 remove()
方法将此条Entry记录删除。
3. 线程池归还线程必须清理Map
如果线程池中的线程归还时,没有清理ThreadLocalMap,则此线程被再次使用的时候,可能会导致Map中的数据发生错误,比如发现Map中已经有同名的key,则不会再插入新值等情况。
边栏推荐
- Seven principles of software design
- 无心剑汉英双语诗003. 《书海》
- 性能测试工具wrk安装使用详解
- 树莓派初步使用
- 嵌入式开发:技巧和窍门——干净地从引导加载程序跳转到应用程序代码
- First order model realizes photo moving (with tool code) | machine learning
- Collapse code using region
- A girl has been making hardware for ten years. 。。
- Structure du disque
- Summary of papers on traveling salesman problem (TSP)
猜你喜欢
These map operations in guava have reduced my code by 50%
零代码即可将数据可视化应用到企业管理中
Practice of hierarchical management based on kubesphere
Notes on writing questions (18) -- binary tree: common ancestor problem
Docker installs MySQL 8.0. Detailed steps
嵌入式开发:技巧和窍门——干净地从引导加载程序跳转到应用程序代码
Industrial development status of virtual human
leetcode:55. 跳跃游戏【经典贪心】
Yida technology signed a contract with seven wolves to help the digital transformation of "Chinese men's wear leader"
Detailed installation and use of performance test tool wrk
随机推荐
Want to be a test leader, do you know these 6 skills?
故障安全移动面板KTP900F Mobile下载程序提示无法下载,目标设备正在运行或未处于传输模式的解决办法
Resolving the conflict problem of the flutter Library
try-with-resources 中的一个坑,注意避让
The leader of ERP software in printing industry
Object. Defineproperty and reflect Fault tolerance of defineproperty
Principle and application of queue implementation
04A interrupt configuration
socket(1)
Short video mall system, how does scroll view adapt to the remaining height of the page
first-order-model实现照片动起来(附工具代码) | 机器学习
嵌入式开发:技巧和窍门——干净地从引导加载程序跳转到应用程序代码
Collective search + drawing creation
网上立案流程
Genesis公链与美国一众加密投资者齐聚Consensus 2022
系统测试主要步骤
字符串习题总结2
Creating files, recursively creating directories
Introduction, installation and use of postman tool
interrupt、interrupted 、isInterrupted 区别