sql做网站,经典品牌推广文案,一流的网站建设,舆情报告目录
一.什么是ThreadLocal
二.ThreadLocal的内部结构
三.ThreadLocal带来的内存泄露问题
▐ key强引用
▐ key弱引用
总结 一.什么是ThreadLocal
在Java中#xff0c;ThreadLocal 类提供了一种方式#xff0c;使得每个线程可以独立地持有自己的变量副本#xff0c;而… 目录
一.什么是ThreadLocal
二.ThreadLocal的内部结构
三.ThreadLocal带来的内存泄露问题
▐ key强引用
▐ key弱引用
总结 一.什么是ThreadLocal
在Java中ThreadLocal 类提供了一种方式使得每个线程可以独立地持有自己的变量副本而不是共享变量。这可以避免线程间的同步问题因为每个线程只能访问自己的ThreadLocal变量。通过ThreadLocal为线程添加的值只能由这个线程访问到其他的线程无法访问因此就避免了多线程之间的同步问题
使用ThreadLocal时通常需要实现以下步骤
初始化创建ThreadLocal变量。 private static ThreadLocalT threadLocal new ThreadLocal();设置值使用set(T value)方法为当前线程设置值。 threadLocal.set(value);获取值使用get()方法获取当前线程的值。 T value threadLocal.get();移除值使用remove()方法在线程结束时清除ThreadLocal变量以避免内存泄漏。 threadLocal.remove();在下面这个示例中在主线程中存储了一个整形的10新建一个线程后去取这个值是取不到的因为该值只属于主线程故输出为null
public class ThreadLocalExample {private static ThreadLocalInteger threadLocal new ThreadLocal();public static void main(String[] args) {// 设置线程局部变量的值threadLocal.set(10);// 这个值在其他线程中是取不到获取的new Thread(() - {Integer value threadLocal.get();//nullSystem.out.println(Thread value: value);}).start();}
} 二.ThreadLocal的内部结构
在JDK8之后每一个线程都会维护一个ThreadLoaclMap这个Map是一个哈希散列结构如下图所示每一个元素Entry都是一个键值对key为ThreadLocalValue为存储的数据也就是set()方法存储的内容。 但是在早期并不是这样的早期的JDK中都是由ThreadLocal来维护这样的一个Map里面的key则是Thread就像下图这样 Thread线程数一般往往是大于ThreadLocal的那么当线程销毁的时候对比俩个方案JDK8的方案则可以节省更多的内存空间只需要将对应的ThreadLocalMap删除JDK8之前的方案由于Thread只是Map的一个节点的key将其释放掉就会导致这块Map的空间利用率很低。
我们也可以打开ThreadLocalMap的核心源码会发现正是JDK8方案所示的结构 以下是添加了中文注释的版本
static class ThreadLocalMap {/*** 存储的每个元素--Entry*/static class Entry extends WeakReferenceThreadLocal? {Object value;Entry(ThreadLocal? k, Object v) {super(k);value v;}}/*** 初始容量--必须是2的整数幂*/private static final int INITIAL_CAPACITY 16;/*** 存放数据的Table长度也必须是2的整数幂*/private Entry[] table;/*** 数组内已使用的长度即Entrys的个数*/private int size 0;/*** 进行扩容的阈值*/private int threshold; // Default to 0
}
三.ThreadLocal带来的内存泄露问题
首先是内存泄漏的概念
内存溢出没有足够的内存供申请者使用内存泄漏程序中已经动态分配的内存由于某种原因未释放或无法释放造成系统内存的浪费导致程序运行速度减慢甚至崩溃。此外内存泄漏的堆积最终也会导致内存溢出。
下图是ThreadLocal相关的内存结构图在栈区中有threadLocal对象和当前线程对象分别指向堆区真正存储的类对象这俩个指向都是强引用。在堆区中当前线程肯定是只有自己的Map的信息的而Map中又存储着一个个的Entry节点在Entry节点中每一个Key都是ThreadLocal的实例同时Value又指向了真正的存储的数据位置以上便是下图的引用关系。 那么所谓的内存泄漏其实就是指的Entry这块内存不能正确释放
有人可能会猜测出现内存泄漏是因为Entry中使用了弱引用的key如下所示继承关系中的WeakReference但这种理解其实是不对的 static class Entry extends WeakReferenceThreadLocal? {Object value;Entry(ThreadLocal? k, Object v) {super(k);value v;}}
强弱引用的概念
强引用(StrongReference)就是我们最常见的普通对象引用只要还有强引用指向一个对象就能表明对象还“活着”垃圾回收器就不会回收这种对象。弱引用(WeakReference)垃圾回收器一旦发现了只具有弱引用的对象不管当前内存空间足够与否都会回收它的内存。
▐ key强引用
我们可以按照强弱引用来分别推算一下首先是强引用的情况
当我们在业务代码中使用完ThreadLocal在栈区指向堆区的这个指向关系就会被回收掉了但是由于Key是强引用指向ThreadLocal故而堆区中的ThreadLocal无法被回收此时的Key指向ThreadLocal另外由于当前线程还没有结束则下面那条强引用指向关系任然存在。故为下图的关系状态 在这样的情况下由于栈上的指向已经消失了我们无法访问到堆上的ThreadLocal故而无法访问到Entry但是Entry又有Map指向它故而无法进行回收。那么此时的Entry即无法访问也无法回收这就造成了Entry的内存溢出。
▐ key弱引用
其次是弱引用的情况当我们在业务代码中使用完ThreadLocal就通过垃圾回收GC进行了回收那么由于Key是弱引用Key此时就指向null但是由于当前线程还没有结束则下面那条强引用指向关系任然存在 在这样的情况下Entry由于仍然有Map指向它所以不会被GC回收掉但是此时的Key又为null所以我们无法访问到这个Value。这就导致了这个Value我们即不能访问到也不能进行回收此时就造成了Value的内存泄漏。
总结
通过以上分析我们得知了不管Entry中的Key是否为弱引用都会造成内存泄漏的情况只不过强引用下是Entry的内存泄漏弱引用下是Value的内存泄漏。造成这样内存泄漏的情况都有这样的共同特性
都没有手动删除Entry当先线程都在运行
也就是说只要我们在使用完ThreadLocal后调用其remove()方法删除对应的Entry就可以避免内存泄漏的问题。
并且由于ThreadLoaclMap是Thread的一个属性故而它的生命周期和线程一样那么当线程的生命周期结束自然也就没有Map指向Entry这也就在根源上解决了问题。
综上所述造成ThreadLoacl内存泄漏的根本原因是由于ThreadLoaclMap的生命周期和Thread一样长如果没有手动删除对应的Key就会导致内存泄漏。 本次的分享就到此为止了希望我的分享能给您带来帮助创作不易也欢迎大家三连支持你们的点赞就是博主更新最大的动力如有不同意见欢迎评论区积极讨论交流让我们一起学习进步有相关问题也可以私信博主评论区和私信都会认真查看的我们下次再见