ThreadLocal和CompletableFuture

ThreadLocal

底层数据结构:ThreadLocal的数据存在Thread对象里,每个Thread都有一个threadLocals字段,它的类型是ThreadLocalMap,这个map的key是ThreadLocal对象本身,value是你存的值!\[Pasted image 20260430103005.png]

为什么需要ThreadLocal

使用ThreadLocal的主要目的是解决多线程环境下的线程安全问题。每个线程都有独立的变量副本,从而避免了多个线程对共享变量的竞争,使得不需要加锁也能保证线程安全。同时它简化了深层调用中的上下文信息传递,用ThreadLocal存一下,哪层需要哪层直接get,实现代码解耦

ThreadLocal是如何实现线程隔离的?

ThreadLocal通过在每个线程内部维护一个ThreadLocalMap来实现线程隔离。该Map以ThreadLocal实例为键,以线程本地变量副本为值。当调用ThreadLocal的get()或set()方法时,实际上是当前线程从中取出或存入自己的那份数据,因此各线程之间互不干扰。

ThreadLocal可能引发内存泄露吗,怎么避免?

可能,因为ThreadLocal的key是弱引用,但value是强引用。很可能ThreadLocal被回收后,而value未及时回收,则会导致内存泄漏,应该调用remove()方法主动清理数据。

你刚才提到 ThreadLocal 会有内存泄漏,那为什么 Entry 的 key 要设计成弱引用,直接用强引用不行吗?

key不能用强引用,会导致ThreadLocal对象永久无法回收,严重内存泄漏,弱引用key保证ThreadLocal对象能及时被GC,缩小泄露范围,真正杜绝必须手动调用

死锁

  • 互斥条件:每个资源要么分配给一个线程,要么可用
  • 占用且等待:一个线程已经占有至少一个资源,而又在等另一个被其他线程占有的资源
  • 不可剥夺:线程占用的资源不能被剥夺,资源只能在使用由线程自己释放
  • 环路等待:线程A等待线程B占有的资源,而C在等待B...从而形成等待环路

解决方案

  • 把资源独享改为共享,可以资源冗余,多搞几份相同资源
  • 一次性请求所需的所有资源
  • 如果一个进程得不到所需的资源就应该释放所持有的资源,或者使用优先级剥夺
  • 对系统中的资源进行排序,线程按序请求资源

排查死锁:jps(查看进程PID)+jstack(打印线程栈),线上用arthas的thread -b一键查出阻塞死锁线程,打印栈信息,锁对象,等待关系

CompletableFuture

老版的Future是用来拿到另一个线程里的任务的执行结果,相当于是一个凭证,

等到需要结果的时候再调用get(),这时候主线程才开始阻塞等待拿结果。

痛点:一是get()方法会阻塞线程,二是没法把异步任务串起来

因此java8引入了CompletableFuture ,支持链式调用,可以用thenApply,thenCompose这些方法把多个异步操作串起来,下一个任务完成自动触发下一个,不用手动阻塞等待结果

常见的坑

  • 忘了处理异常:如果不加 exceptionally(只接异常) 或 handle(成功和异常全接,成功再加工,失败来兜底),异常会被吞掉,二者选其一就行。
  • get 超时:虽然 CompletableFuture 主打不阻塞,但有时候还是得等结果。用 get(timeout, unit) 而不是 get(),避免无限等待
  • 回调里阻塞:在 thenApply 这些回调里搞阻塞操作,会拖慢整个线程池。如果必须阻塞,用 thenApplyAsync 把后续操作扔到独立线程池
  • allOf 拿不到结果:allOf 返回的是 CompletableFuture<Void>,拿结果得用 join 逐个取

如何组合多个 CompletableFuture?

可以使用 thenCombine 合并两个独立的 CompletableFuture 的结果;

使用 thenCompose 实现异步任务的串联,将一个异步任务的结果作为下一个异步任务的输入。

thenApply 和 thenApplyAsync 有什么区别?

thenApply 用的是上个任务的执行线程,如果任务已完成,就用当前调用 thenApply 的线程。thenApplyAsync 重新从线程池拿一个新线程异步干,默认是 commonPool,也可以传自定义线程池。如果回调里有耗时操作,用 Async 版本避免阻塞。

怎么实现多个异步任务,任意一个成功就返回?

用 anyOf,但它有个问题,异常也算完成。如果想要第一个成功的结果,得自己写逻辑,用一个 CompletableFuture 作为结果容器,每个任务完成时调 complete,因为 complete 只有第一次调用生效,后面的会被忽略。

生产环境用 CompletableFuture 有什么注意事项?

回答:几个关键点:一是必须用自定义线程池,不要用 commonPool;二是必须处理异常,不然异常会被吞;三是设置合理的超时,避免无限等待;四是监控线程池状态,队列堆积要报警。另外日志里要打印任务执行线程名,方便排查问题。

相关推荐
小羊没烦恼!25 分钟前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
Cx330❀1 小时前
【Linux网络】网络层协议 IP :从网络层原理到 Linux 内核源码
linux·运维·服务器·网络·tcp/ip·ai·ai编程
小荷才露尖尖角,早有蜻蜓立上头1 小时前
过滤器的4种创建方式
java
大圣编蚕2 小时前
Java ByteArrayInputStream 详解:从入门到实战
java·开发语言·算法
程序员阿明2 小时前
idea把插件啥的不默认放在C盘
java·ide·intellij-idea
linweidong3 小时前
中科闻歌Java面试题及参考答案
java·python·大模型·提示词·ai agent·mcp·rag原理
OpenAnolis小助手3 小时前
一句话看透 JVM,SysOM 诊断 Skill 新增 Java 应用诊断能力
java·开发语言·jvm·阿里云·操作系统控制台·sysom
ForteScarlet3 小时前
Kotlin 2.4.20 现已发布,新特性多不多?
android·java·开发语言·开源·kotlin·jetbrains
FPC工厂——皇榜科技3 小时前
机器人灵巧手线路板:从“握得住”到“摸得准”:一只机械手的柔性神经密码
网络·人工智能·科技·机器人·pcb工艺
萧瑟余晖3 小时前
Java深入解析篇六十二之安全机制详解
java·开发语言