news 2026/5/1 5:16:32

Java ThreadLocal 内存泄漏代码示例

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java ThreadLocal 内存泄漏代码示例

ThreadLocal不加static修饰时,会发生每个实例都有自己的 ThreadLocal 对象,这会导致严重的线程数据混乱问题。

本篇博文我通过如下代码来演示这个问题:

修改后的示例:不加static的 ThreadLocal

importjava.util.ArrayList;importjava.util.List;classSharedObject{privateintcounter=0;publicvoidincrement(){counter++;}publicintgetCounter(){returncounter;}}classThreadLocalNonStaticExample{// 注意:这里去掉了 static 修饰符!privateThreadLocal<SharedObject>threadLocal=ThreadLocal.withInitial(()->newSharedObject());publicvoidupdateCounter(){SharedObjectshared=threadLocal.get();shared.increment();System.out.println(Thread.currentThread().getName()+" - Counter: "+shared.getCounter()+" - ThreadLocal hash: "+System.identityHashCode(threadLocal));}publicstaticvoidmain(String[]args)throwsInterruptedException{// 创建两个不同的实例ThreadLocalNonStaticExampleexample1=newThreadLocalNonStaticExample();ThreadLocalNonStaticExampleexample2=newThreadLocalNonStaticExample();System.out.println("两个实例的 ThreadLocal 对象是否相同: "+(example1.threadLocal==example2.threadLocal));System.out.println("example1.threadLocal hash: "+System.identityHashCode(example1.threadLocal));System.out.println("example2.threadLocal hash: "+System.identityHashCode(example2.threadLocal));Threadthread1=newThread(()->{System.out.println("\n=== 线程1执行 ===");// 同一个线程,但访问不同实例的 ThreadLocalexample1.updateCounter();// 输出 1example1.updateCounter();// 输出 2example2.updateCounter();// 输出 1 - 因为是不同的 ThreadLocal!},"Thread-1");Threadthread2=newThread(()->{System.out.println("\n=== 线程2执行 ===");example1.updateCounter();// 输出 1 - 新线程,新的 ThreadLocalMapexample2.updateCounter();// 输出 1},"Thread-2");thread1.start();thread1.join();thread2.start();thread2.join();}}

更复杂的场景:内存泄漏风险

classMemoryLeakExample{// 非 static 的 ThreadLocalprivateThreadLocal<List<String>>dataThreadLocal=ThreadLocal.withInitial(()->newArrayList<>());publicvoidaddData(Stringdata){dataThreadLocal.get().add(data);}publicvoidprintData(){System.out.println(Thread.currentThread().getName()+" 数据: "+dataThreadLocal.get());}publicstaticvoidmain(String[]args)throwsInterruptedException{// 模拟大量创建实例List<MemoryLeakExample>examples=newArrayList<>();Threadthread=newThread(()->{for(inti=0;i<1000;i++){MemoryLeakExampleexample=newMemoryLeakExample();examples.add(example);example.addData("data-"+i);if(i%100==0){System.out.println("创建了 "+i+" 个实例");System.out.println("当前线程 ThreadLocalMap 中的 Entry 数量会不断增加...");}}// 每个实例都有自己的 ThreadLocal 对象作为 key// 在 ThreadLocalMap 中会有大量 Entry!});thread.start();thread.join();System.out.println("\n总计创建了 "+examples.size()+" 个实例");System.out.println("这意味着在当前线程的 ThreadLocalMap 中有 "+examples.size()+" 个不同的 ThreadLocal key!");}}

在线程池中的灾难性后果

importjava.util.concurrent.ExecutorService;importjava.util.concurrent.Executors;classThreadPoolProblem{// 非 static ThreadLocalprivateThreadLocal<Integer>counterThreadLocal=ThreadLocal.withInitial(()->0);publicvoidincrement(){Integercount=counterThreadLocal.get();counterThreadLocal.set(count+1);}publicintgetCount(){returncounterThreadLocal.get();}publicstaticvoidmain(String[]args)throwsInterruptedException{ExecutorServiceexecutor=Executors.newFixedThreadPool(2);// 任务:每个任务创建一个新实例for(inti=0;i<10;i++){inttaskId=i;executor.submit(()->{ThreadPoolProbleminstance=newThreadPoolProblem();instance.increment();instance.increment();System.out.println("任务 "+taskId+",线程: "+Thread.currentThread().getName()+",计数: "+instance.getCount()+",ThreadLocal hash: "+System.identityHashCode(instance.counterThreadLocal));// 问题:线程池线程复用,但每次任务都创建新的 ThreadLocal// 导致 ThreadLocalMap 中积累大量 stale entries});}executor.shutdown();}}

不加static的问题总结

  1. 线程隔离失效

    • 每个实例有自己的ThreadLocal对象
    • 同一个线程访问不同实例时,会使用不同的ThreadLocalkey
    • 无法实现真正意义上的线程级数据共享
  2. 内存泄漏

    // 每次创建新实例ThreadLocalNonStaticExampleobj=newThreadLocalNonStaticExample();// 都会创建新的 ThreadLocal 对象// ThreadLocalMap 中会积累:ThreadLocal(key) -> value// 线程不结束,这些 entry 就一直存在
  3. ThreadLocalMap 膨胀

    • 每个线程的ThreadLocalMap中会有大量 key(每个实例一个)
    • 即使实例被回收,ThreadLocal 是弱引用,但 value 可能还在
  4. 数据不一致

    // 线程1example1.updateCounter();// 使用 example1.threadLocalexample2.updateCounter();// 使用 example2.threadLocal(不同的存储位置!)// 它们不是同一个 ThreadLocal,所以不共享数据
  5. 性能问题

    • 每次都要创建新的ThreadLocal对象
    • ThreadLocalMap查找效率降低(hash 冲突增加)

正确 vs 错误对比

// ✅ 正确:static ThreadLocalpublicclassCorrectExample{privatestaticThreadLocal<Context>context=ThreadLocal.withInitial(Context::new);// 所有实例共享同一个 ThreadLocal 对象// 线程数据真正隔离}// ❌ 错误:非 static ThreadLocalpublicclassWrongExample{privateThreadLocal<Context>context=ThreadLocal.withInitial(Context::new);// 每个实例都有自己的 ThreadLocal// 线程数据混乱}

结论ThreadLocal必须使用static修饰,确保所有实例共享同一个ThreadLocal对象作为 key,这样才能正确实现线程级别的数据隔离。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/18 20:05:55

实验室净化哪家强?靠谱企业大揭秘

实验室净化哪家强&#xff1f;靠谱企业大揭秘前言在现代科学研究和工业生产中&#xff0c;实验室净化的作用日益凸显。一个高效、可靠的实验室净化系统不仅能保障实验数据的准确性&#xff0c;还能确保实验人员的安全。那么&#xff0c;在众多的实验室净化企业中&#xff0c;哪…

作者头像 李华
网站建设 2026/4/18 12:07:57

使用Miniconda-Python3.10镜像在Jupyter中运行PyTorch代码的完整步骤

使用Miniconda-Python3.10镜像在Jupyter中运行PyTorch代码的完整步骤 在现代人工智能开发中&#xff0c;一个常见的痛点是&#xff1a;明明代码写得没问题&#xff0c;却因为“环境不一致”导致无法运行。你是否也遇到过这样的情况——同事发来一份 PyTorch 项目&#xff0c;在…

作者头像 李华
网站建设 2026/4/22 21:24:22

Miniconda环境命名空间隔离:避免命名冲突

Miniconda环境命名空间隔离&#xff1a;避免命名冲突 在AI项目开发中&#xff0c;你是否曾遇到过这样的场景&#xff1f;刚跑通一个基于PyTorch 1.12的实验&#xff0c;准备切换到另一个使用TensorFlow 2.8的项目时&#xff0c;却发现import tensorflow报错版本不兼容。深入排查…

作者头像 李华
网站建设 2026/4/23 13:15:11

Node.js用readline逐行读大文件内存不爆

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 Node.js事件循环&#xff1a;解锁异步编程的奥秘目录Node.js事件循环&#xff1a;解锁异步编程的奥秘 引言&#xff1a;为什么事…

作者头像 李华
网站建设 2026/4/26 14:18:13

vue基于django的剧本杀迷雾探案馆经营管理系统的设计与实现

目录已开发项目效果实现截图关于博主开发技术路线相关技术介绍核心代码参考示例结论源码lw获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;已开发项目效果实现截图 同行可拿货,招校园代理 ,本人源头供货商 vue基于django的剧本杀迷雾探案馆经营…

作者头像 李华
网站建设 2026/4/18 4:49:23

解码GPIO、寄存器与蜂鸣器(三极管)

GPIO外设接口原理 GPIO&#xff08;通用输入输出端口&#xff09;是STM32最基础的外设&#xff0c;可通过软件配置为输入、输出、复用或模拟模式&#xff0c;用于连接LED、按键、传感器等外部器件。其核心配置流程为&#xff1a;定义初始化结构体 → 开启外设时钟 → 配置结构体…

作者头像 李华