news 2026/2/27 1:53:37

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/2/25 0:21:31

环境仿真软件:AnyLogic_(9).事件与时间控制

事件与时间控制 在仿真软件中&#xff0c;事件与时间控制是实现系统动态行为的关键机制。AnyLogic 提供了多种方式来管理和控制仿真时间&#xff0c;包括事件触发、时间进度控制、定时器等。通过合理地设置事件与时间控制&#xff0c;可以确保仿真的准确性和高效性。本节将详细…

作者头像 李华
网站建设 2026/2/26 9:27:14

Miniconda环境下如何升级Python到最新补丁版本?

Miniconda 环境下如何安全升级 Python 补丁版本 在数据科学与 AI 工程实践中&#xff0c;一个看似微不足道的操作——将 Python 从 3.10.6 升级到 3.10.12——可能直接关系到模型训练的稳定性、安全漏洞的修复&#xff0c;甚至是整个团队环境的一致性。这并不是简单的“更新软件…

作者头像 李华
网站建设 2026/2/26 14:24:40

在Miniconda环境中使用nb_conda_kernels管理多个内核

在Miniconda环境中使用nb_conda_kernels管理多个内核 在数据科学和人工智能项目日益复杂的今天&#xff0c;开发者常常面临一个看似简单却极易引发混乱的问题&#xff1a;如何在一个Jupyter界面中安全、高效地运行多个依赖不同Python版本或AI框架的项目&#xff1f;更具体地说&…

作者头像 李华
网站建设 2026/2/26 10:42:58

GameAssist智能游戏助手:从菜鸟到高手的秘密武器

GameAssist&#xff1a;智能游戏助手&#xff0c;从菜鸟到高手的“秘密武器”&#xff1f; “GameAssist智能游戏助手:从菜鸟到高手的秘密武器”这个短语听起来像是一款强大AI工具的宣传语&#xff0c;能帮助新手玩家快速提升到高手水平。但根据最新信息&#xff0c;这个主要指…

作者头像 李华
网站建设 2026/2/24 17:48:20

免费听音乐,下载音乐mp3,mp4,歌词的网站分享

免费听音乐、下载MP3/MP4及歌词的网站推荐&#xff08;2025最新&#xff09; 以下是目前&#xff08;2025年底&#xff09;仍活跃且实用的免费音乐资源网站推荐。我优先选择了合法或半合法的平台&#xff08;如Creative Commons授权的独立音乐&#xff09;&#xff0c;以及一些…

作者头像 李华
网站建设 2026/2/25 16:12:05

远程调试Python:pdb连接Miniconda容器内程序

远程调试Python&#xff1a;pdb连接Miniconda容器内程序 在现代AI与数据科学项目中&#xff0c;开发者常常面临一个尴尬的现实&#xff1a;代码在本地运行完美&#xff0c;一旦部署到远程服务器或容器环境却频频报错。日志里只留下一句模糊的 ZeroDivisionError&#xff0c;而你…

作者头像 李华