ThreadLocal数据结构、内存泄漏分析

news/2024/12/22 3:07:36/

文章目录

  • ⚽ThreadLocal
    • 🎉入门案例
    • 🎈ThreadLocal在线程中怎么存储的
    • 🎗为什么会造成内存泄漏?
    • 🎃ThreadLocalMap的key使用强引用和弱引用有什么区别呢?
    • 🔔补充说明
      • Java中引用类型分类
      • 内存泄漏和内存溢出区别

⚽ThreadLocal

用来为每个线程提供独立的变量副本的

🎉入门案例

使用案例

java">public class ThreadLocalMultipleExample {private static final ThreadLocal<Integer> threadLocal1 = ThreadLocal.withInitial(() -> 1);private static final ThreadLocal<String> threadLocal2 = ThreadLocal.withInitial(() -> "Hello");public static void main(String[] args) {// 线程A设置值Thread threadA = new Thread(() -> {threadLocal1.set(10);threadLocal2.set("Thread A");System.out.println("Thread A - local1: " + threadLocal1.get()); // 10System.out.println("Thread A - local2: " + threadLocal2.get()); // Thread A});// 线程B设置值Thread threadB = new Thread(() -> {System.out.println("Thread B - local1: " + threadLocal1.get()); // 1System.out.println("Thread B - local2: " + threadLocal2.get()); // Hello});threadA.start();threadB.start();}
}

运行结果:可以看到虽然线程A修改了变量的值,但是在线程B中变量的值还是初始给的值,因为这两个变量在每个线程中都有自己的副本

java">Thread A - local1: 10
Thread A - local2: Thread A
Thread B - local1: 1
Thread B - local2: Hello

🎈ThreadLocal在线程中怎么存储的

先来了解ThreadLocal在Thread时怎么存储的。

我们先看Thread的源码,里面有个ThreadLocalMap类型的变量。

java">public class Thread implements Runnable {ThreadLocal.ThreadLocalMap threadLocals = null;
}

从下面ThreadLocal的部分源码中可以看出,这个ThreadLocalMap类是ThreadLocal类中静态内部类。我们可以把ThreadLocalMap当做一个Map,其中ThreadLocalkey,存储的值就是value

可以看到ThreadLocalset、get方法,都是用Thread.currentThread()获取当前线程后,拿到每个线程自己独有的ThreadLocalMap之后进行读写操作,所以这里保证了每个线程都有自己的ThreadLocal副本。

java">public class ThreadLocal<T> {// ......省略部分代码static class ThreadLocalMap {static class Entry extends WeakReference<ThreadLocal<?>> {Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}}// ......省略部分代码public T get() {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}return setInitialValue();}// ......省略部分代码public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {map.set(this, value);} else {createMap(t, value);}}
}

ThreadLocalMapHashMap在存储结构上有些不同,HashMap是数组+链表(+红黑树)的形式,但是ThreadLocalMap是纯数组的形式,内部只有一个Entry[] table数组,其中一个Entry就是一个键值对。

使用ThreadLocal时需要注意避免出现内存泄漏问题。

🎗为什么会造成内存泄漏?

我们从源码中可以看出ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal不存在外部强引用时,Key(ThreadLocal)势必会被GC回收,这样就会导致ThreadLocalMap中key为null, 而value还存在着强引用,只有当thead线程退出以后,value的强引用链条才会断掉。意味着这个线程一直不结束的话,这个value就一直无法回收,造成内存泄漏,这种情况一般发生在使用线程池的场景中,因为里面的线程正常情况下会一直存活。

在平时使用ThreadLocal类时,要避免内存泄漏问题,可以在线程处理完任务后,使用threadLocal.remove()方法,移除当前threadLocal

🎃ThreadLocalMap的key使用强引用和弱引用有什么区别呢?

  • key 使用强引用:当ThreadLocalMap的key为强引用,发生GC时,因为ThreadLocalMap还持有ThreadLocal的强引用,同时ThreadLocalMap和Thread生命周期相同,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。
  • key 使用弱引用:当ThreadLocalMap的key为弱引用,发生GC时,由于ThreadLocalMap持有的是ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。当key为null,在下一次其他ThreadLocal调用他们的set(),get(),remove()方法的时候都会清除key为null对应的value值。

🔔补充说明

Java中引用类型分类

  • 强引用:我们常常 new 出来的对象就是强引用类型,只要强引用存在,垃圾回收器将永远不会回收被引用的对象,哪怕内存不足的时候
  • 软引用:使用 SoftReference 修饰的对象被称为软引用,软引用指向的对象在内存要溢出的时候被回收
  • 弱引用:使用 WeakReference 修饰的对象被称为弱引用,只要发生垃圾回收,若这个对象只被弱引用指向,那么就会被回收
  • 虚引用:虚引用是最弱的引用,在 Java 中使用 PhantomReference 进行定义。虚引用中唯一的作用就是用队列接收对象即将死亡的通知

内存泄漏和内存溢出区别

  • 内存泄漏 是因为程序没有释放不再使用的内存,导致内存逐渐积累,最终可能引起内存溢出。
  • 内存溢出 是当程序请求的内存超出了系统可分配的最大值时,操作系统无法满足内存请求,从而导致程序崩溃。

http://www.ppmy.cn/news/1557089.html

相关文章

技术分享 —— JMeter接口与性能测试实战!

前言 在软件开发和运维过程中&#xff0c;接口性能测试是一项至关重要的工作。JMeter作为一款开源的Java应用&#xff0c;被广泛用于进行各种性能测试&#xff0c;包括接口性能测试。本文将详细介绍如何使用JMeter进行接口性能测试的过程和步骤。 JMeter是Apache组织开发的基…

Redis——缓存双写一致性问题

文章目录 1、情况描述2、缓存双写一致性2.1 情况讨论2.2 双检加锁2.3 数据库和缓存一致性的几种更新策略。 总结 1、情况描述 默认不存在缓存雪崩和缓存击穿情况。首先Java先查询redis&#xff0c;若redis中存在数据则直接返回数据。若redis中不存在数据&#xff0c;需要查询my…

包子凑数(2017年蓝桥杯试题H)

【问题描述】 小明几乎每天早晨都会在一家包子铺吃早餐&#xff0c;他发现这家包子铺有N种蒸笼&#xff0c;其中第i种蒸笼恰好能放Ai(i为下标)个包子。每种蒸笼都有非常多个&#xff0c;可以认为是无限笼。 每当有顾客想买X个包子。卖包子的大叔就会迅速选出若干笼包子&#xf…

Git Rebase分支合并

Git Rebase 的应用场景&#xff0c;包括如何合并多 次提交记录和分支合并 // 1.分支合并 从master切一个开发分支 feature1, git checkout -b feature1 // 2.代码开发完提交代码完后&#xff0c;回到master拉取最新代码 git checkout master git pull // 3.回到feature1 进行代…

黑客术语3

19、免杀 : 就是通过加壳、加密、修改特征码、加花指令等等技术来修改程序&#xff0c; 使其逃过杀毒软件的查杀。 20 、加壳 : 就是利用特殊的算法&#xff0c;将 EXE 可执行程序或者 DLL 动态连接库文件的 编码进行改变&#xff08;比如实现压缩、加密&#xff09;&a…

Vue 滚动条样式

模板 <template><div class"box"><div v-for"(item, index) in 100" :key"index" class"box_item">{{ item }}</div></div> </template>样式 <style lang"scss" scoped> .box …

Dhatim FastExcel 读写 Excel 文件

Dhatim FastExcel 读写 Excel 文件 一、说明1、主要特点2、应用场景 二、使用方法1、引入依赖2、Sheet 数据3、读取 Excel4、写入 Excel 一、说明 Github 地址&#xff1a;Dhatim FastExcel Dhatim FastExcel是一个高性能、轻量级的Java库&#xff0c;专门用于读取和写入Exce…

OpenHarmony-4.HDI 框架

HDI 框架 1.HDI介绍 HDI&#xff08;Hardware Device Interface&#xff0c;硬件设备接口&#xff09;是HDF驱动框架为开发者提供的硬件规范化描述性接口&#xff0c;位于基础系统服务层和设备驱动层之间&#xff0c;是连通驱动程序和系统服务进行数据流通的桥梁&#xff0c;是…