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;二是必须处理异常,不然异常会被吞;三是设置合理的超时,避免无限等待;四是监控线程池状态,队列堆积要报警。另外日志里要打印任务执行线程名,方便排查问题。

相关推荐
NiceCloud喜云2 分钟前
Opus 4.8 的 Effort Control 怎么选:Low 到 Max 五档策略
android·java·大数据·前端·c++·python·spring
ylscode36 分钟前
PureLogs 信息窃取恶意软件惊现高危变种:借道 MsBuild.exe 进程空心化实施无痕攻击
网络·安全·安全威胁分析
IPHWT 零软网络36 分钟前
MX60E-A信创级智能语音网关技术实现与架构分析
网络·网络安全·国产自研·技术实现·智能语音网关·政企通信·信创技术
_日拱一卒1 小时前
LeetCode:994腐烂的橘子
java·数据结构·算法·leetcode·深度优先
隔窗听雨眠1 小时前
Nginx网关响应慢排查手记
java·服务器·nginx
IT大白鼠1 小时前
RSTP协议原理与配置详解:快速生成树技术的深度解析
网络·网络协议
智慧物业老杨2 小时前
智慧物业合同周期管理系统:从风险预警到智能交接的全流程数智化落地方案
java·人工智能·python
源码宝2 小时前
MES系统源码:Java8 + SpringBoot2.7 + MySQL8 + Redis,后端源码清爽易扩展
java·后端·源码·springboot·mes系统·源码二开·mes源码
JAVA社区2 小时前
Java高级全套教程(十)—— SpringCloudAlibaba超详细实战详解
java·开发语言·spring cloud·面试·职场和发展
金銀銅鐵2 小时前
[Java] 如何理解 class 文件中方法的 descriptor?
java·后端