【Future CompletableFuture 详解】
·
一、Future详解
1.future是什么
future是对异步执行结果的一种抽象,本质是:一个占位符,代表将来某一时刻可以拿到的结果
Future<V>
提交一个任务,线程池立刻返回future,任务在后台执行
2.future的核心能力
| 方法 | 含义 |
|---|---|
| get() | 阻塞等待结果 |
| get(timeout) | 超时等待 |
| cancel() | 尝试取消任务 |
| isDone() | 是否完成 |
| isCancelled() | 是否被取消 |
3.future的致命缺陷
(1)无法非阻塞获取结果
future.get(); // 必须阻塞
(2)无法链式编排:多个异步任务无法优雅组合
(3)无法回调通知:任务完成后,不能主动通知
二、CompletableFuture 执行异步任务
1.CompletableFuture 的定位
Future + Callback + Stream + DAG(任务图)
2.创建异步任务的三种方式
2.1 无返回值任务(Runnable)
CompletableFuture.runAsync(() -> {
System.out.println("异步任务执行");
});
2.2 有返回值任务(Supplier)
CompletableFuture<Integer> future =
CompletableFuture.supplyAsync(() -> {
return 100;
});
2.3 使用自定义线程池
ExecutorService executor = Executors.newFixedThreadPool(10);
CompletableFuture<Integer> future =
CompletableFuture.supplyAsync(() -> {
return 100;
}, executor);
默认使用 ForkJoinPool.commonPool(),不适合 IO 密集业务
三、CompletableFuture 结果处理
1.同步处理(thenApply)
future.thenApply(result -> result * 2);
- 有返回值
- 不切线程
- 可能阻塞当前线程
2.异步处理(thenApplyAsync)
future.thenApplyAsync(result -> result * 2);
- 会切线程
- 可指定线程池
3.消费结果(thenAccept)
future.thenAccept(result -> {
System.out.println(result);
});
4.无关结果(thenRun)
future.thenRun(() -> {
System.out.println("任务完成");
});
5.异常处理
5.1 exceptionally(兜底)
future.exceptionally(ex -> {
log.error("异常", ex);
return -1;
});
5.2 handle(结果 + 异常)
future.handle((result, ex) -> {
if (ex != null) return -1;
return result;
});
exceptionally 只处理异常
handle 无论成功失败都会执行
四、CompletableFuture 任务交互
1.顺序依赖(thenCompose)
CompletableFuture<Integer> f =
CompletableFuture.supplyAsync(() -> 10)
.thenCompose(r ->
CompletableFuture.supplyAsync(() -> r * 2)
);
上一个结果作为下一个任务的输入(flatMap)
2.并行任务合并(thenCombine)
CompletableFuture<Integer> f1 = ...
CompletableFuture<Integer> f2 = ...
f1.thenCombine(f2, Integer::sum);
3.任意完成(applyToEither)
f1.applyToEither(f2, r -> r);
4.等待所有任务
CompletableFuture.allOf(f1, f2).join();
五、ThreadLocal 的使用
1.ThreadLocal 是什么
为每个线程提供一份变量副本
ThreadLocal<String> tl = new ThreadLocal<>();
tl.set("userId");
以空间换线程安全
2.典型使用场景
- 用户上下文(userID,token)
- 数据源切换
- traceID / 日志链路追踪
- 事务上下文
六、ThreadLocal vs synchronized
| 维度 | ThreadLocal | synchronized |
|---|---|---|
| 作用 | 线程隔离 | 线程互斥 |
| 是否加锁 | 不加锁 | 加锁 |
| 性能 | 高 | 相对低 |
| 数据共享 | 不共享 | 共享 |
| 使用场景 | 上下文 | 临界区 |
ThreadLocal 解决“每个线程一份数据”
synchronized 解决“多个线程抢一份数据”
七、ThreadLocal 内部结构详解
1.核心结构关系
Thread
└── ThreadLocalMap
└── Entry[] table
├── Entry(ThreadLocal, value)
ThreadLocalMap 不在 ThreadLocal 里,而是在 Thread 对象里
2.Entry 结构
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
}
📌 Key:弱引用 ThreadLocal
📌 Value:强引用
八、ThreadLocal 核心方法源码分析
1.set()
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = t.threadLocals;
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
2.get()
Entry e = map.getEntry(this);
return (T) e.value;
3.remove()
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
用完必须 remove(),尤其是线程池场景
九、ThreadLocal 内存泄漏原因分析
1.泄漏本质:Key 被 GC,Value 仍然被线程强引用
Thread
└── ThreadLocalMap
└── Entry
├── key = null
└── value = 大对象
2.为什么线程池更危险
- 线程不会销毁
- Entry 长期存在
- value 无法释放
3.如何避免
必须调用 remove()
try {
tl.set(x);
} finally {
tl.remove();
}
十、ThreadLocal 如何解决 Hash 冲突
1.底层是数组 + 开放寻址法
Entry[] table;
hash计算
int index = threadLocalHashCode & (len - 1);
2.冲突处理策略
index = nextIndex(index, len);
3.过期 Entry 清理机制
- set / get / remove 时触发
- 清理 key = null 的 Entry
- 非实时,惰性清理
总结
1.为什么 CompletableFuture 比 Future 强?
- 支持回调
- 支持任务编排
- 支持异常链路处理
- 支持非阻塞
2.thenApply 和 thenCompose 区别?
- thenApply:map
- thenCompose:flatMap,解决嵌套 Future
3.ThreadLocal 为什么会内存泄漏?
- Key 是弱引用
- Value 是强引用
- 线程长期存活(线程池)
4.ThreadLocal 和 synchronized 的本质区别?
- ThreadLocal:数据隔离
- synchronized:访问互斥
5.ThreadLocal 用在哪些生产场景?
- 用户上下文
- 日志 TraceId
- 数据源切换
- 事务上下文
更多推荐




所有评论(0)