一、ThreadLocal
Q1:什么是 ThreadLocal?
答: ThreadLocal 提供线程本地变量,每个线程都有自己独立的副本,互不干扰。
核心思想:以空间换时间,用线程隔离替代同步。
ThreadLocal<String> tl = new ThreadLocal<>();
// 线程 A
new Thread(() -> {
tl.set("A的值");
System.out.println(tl.get()); // A的值
}).start();
// 线程 B
new Thread(() -> {
tl.set("B的值");
System.out.println(tl.get()); // B的值
}).start();Q2:ThreadLocal 的底层原理是什么?
答: 每个 Thread 内部维护一个 ThreadLocalMap:
public class Thread {
ThreadLocal.ThreadLocalMap threadLocals = null;
}
public class ThreadLocal<T> {
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = t.threadLocals;
if (map != null)
map.set(this, value); // key 是 ThreadLocal 对象
else
t.threadLocals = new ThreadLocalMap(this, value);
}
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = t.threadLocals;
if (map != null) {
Entry e = map.getEntry(this);
if (e != null) return (T) e.value;
}
return setInitialValue();
}
}结构:
Thread
└── ThreadLocalMap
└── Entry[]
├── Entry(key=ThreadLocal1, value=值1)
└── Entry(key=ThreadLocal2, value=值2)关键:
数据存在线程里,不是 ThreadLocal 里
ThreadLocal 作为 key
每个线程独立,天然线程安全
Q3:ThreadLocalMap 的 key 为什么是弱引用?
答: Entry 继承 WeakReference<ThreadLocal<?>>:
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k); // key 是弱引用
value = v; // value 是强引用
}
}为什么用弱引用:
若 key 是强引用,ThreadLocal 对象即使外部不再使用,也无法被 GC
因为 Thread → ThreadLocalMap → Entry → key(ThreadLocal) 强引用链
弱引用让 ThreadLocal 在无外部引用时可被回收
但引入新问题:key 被回收后,value 仍被强引用 → 内存泄漏。
Q4:ThreadLocal 为什么会内存泄漏?
答: 关键在于 Entry 的 key 是弱引用,value 是强引用:
Thread → ThreadLocalMap → Entry → value(强引用)
↓
key(弱引用,可能被回收)泄漏过程:
ThreadLocal 外部引用置 null
GC 回收 key(弱引用)
Entry 的 key 变成 null
但 value 仍被 Entry 强引用
Thread 长期存活(如线程池),value 无法回收
内存泄漏
ThreadLocal<byte[]> tl = new ThreadLocal<>();
tl.set(new byte[1024 * 1024]); // 1MB
tl = null; // key 可被回收,但 value 还在Q5:为什么 value 不用弱引用?
答: 因为 value 需要被正常使用:
若 value 也是弱引用,可能在使用中被 GC 回收
导致
get()返回 null,逻辑错误所以 value 必须强引用
根本矛盾:
key 弱引用:避免 ThreadLocal 对象泄漏
value 强引用:保证数据可用
结果:key 回收后 value 泄漏
Q6:如何避免 ThreadLocal 内存泄漏?
答: 核心是用完必须 remove():
ThreadLocal<User> userHolder = new ThreadLocal<>();
try {
userHolder.set(user);
doWork();
} finally {
userHolder.remove(); // 必须清理
}其他措施:
try-finally 保证 remove
线程池场景尤其重要(线程复用)
用 static final 修饰 ThreadLocal(避免频繁创建)
private static final ThreadLocal<User> USER_HOLDER =
new ThreadLocal<>();Q7:ThreadLocalMap 如何避免泄漏?
答: 通过探测式清理和启发式清理:
get() 时的清理:
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e); // 清理 key=null 的 Entry
}set() 时的清理:
cleanSomeSlots():启发式扫描,清理部分expungeStaleEntry():探测式清理,遇到 null key 就清除
注意:这些清理不保证彻底,仍需手动 remove。
Q8:ThreadLocal 的 hash 冲突如何解决?
答: 用开放地址法(线性探测):
// 计算索引
int i = key.threadLocalHashCode & (len - 1);
// 冲突则往后找
for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
// ...
}对比 HashMap:
HashMap:数组 + 链表/红黑树
ThreadLocalMap:纯数组 + 开放地址法
原因:ThreadLocal 数量通常少,开放地址法更简单高效。
Q9:ThreadLocal 的初始值如何设置?
答: 三种方式:
1. setInitialValue():
ThreadLocal<String> tl = new ThreadLocal<>() {
@Override
protected String initialValue() {
return "默认值";
}
};2. withInitial()(推荐):
ThreadLocal<String> tl = ThreadLocal.withInitial(() -> "默认值");3. 手动 set:
tl.set("值");Q10:ThreadLocal 的典型应用场景?
答:
用户上下文:
private static final ThreadLocal<User> USER = new ThreadLocal<>();
// 请求入口 set,出口 remove数据库连接:
private static final ThreadLocal<Connection> CONN = new ThreadLocal<>();事务管理:
private static final ThreadLocal<Transaction> TX = new ThreadLocal<>();SimpleDateFormat(非线程安全):
private static final ThreadLocal<SimpleDateFormat> SDF =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));MDC 日志追踪:traceId 传递
Q11:ThreadLocal 和 synchronized 有什么区别?
答:
// ThreadLocal:各用各的
ThreadLocal<SimpleDateFormat> sdf = ...;
// synchronized:共享一把锁
synchronized (sdf) { sdf.format(date); }Q12:线程池中使用 ThreadLocal 有什么坑?
答: 线程复用导致数据串扰:
// 线程池
executor.execute(() -> {
userHolder.set(userA);
// 任务结束未 remove
});
// 后续任务复用同一线程
executor.execute(() -> {
User u = userHolder.get(); // 可能拿到 userA!
});解决:
try {
userHolder.set(user);
doWork();
} finally {
userHolder.remove(); // 必须清理
}最佳实践:用 AOP 或拦截器统一清理。
Q13:InheritableThreadLocal 是什么?
答: 支持父子线程传递的 ThreadLocal:
InheritableThreadLocal<String> tl = new InheritableThreadLocal<>();
tl.set("父线程的值");
new Thread(() -> {
System.out.println(tl.get()); // 父线程的值
}).start();原理:创建子线程时,复制父线程的 inheritableThreadLocals。
注意:
线程池不适用(线程复用,不重新创建)
需用
TransmittableThreadLocal(阿里 TTL)
Q14:TransmittableThreadLocal 是什么?
答: 阿里开源的 TTL,解决线程池中上下文传递:
TransmittableThreadLocal<String> tl = new TransmittableThreadLocal<>();
tl.set("值");
// 包装线程池
ExecutorService pool = TtlExecutors.getTtlExecutorService(
Executors.newFixedThreadPool(3));
pool.execute(() -> {
System.out.println(tl.get()); // 正确传递
});原理:任务提交时捕获上下文,执行前设置,执行后恢复。
二、Fork/Join
Q15:什么是 Fork/Join 框架?
答: Fork/Join 是 JDK 7 引入的并行计算框架,用于分治任务:
Fork:把大任务拆成小任务
Join:合并小任务结果
ForkJoinPool pool = new ForkJoinPool();
Long result = pool.invoke(new SumTask(1, 1000000));核心思想:分而治之 + 工作窃取。
Q16:Fork/Join 的核心组件是什么?
答:
class SumTask extends RecursiveTask<Long> {
private final int start, end;
protected Long compute() {
if (end - start <= 1000) {
// 小任务直接计算
long sum = 0;
for (int i = start; i <= end; i++) sum += i;
return sum;
}
int mid = (start + end) / 2;
SumTask left = new SumTask(start, mid);
SumTask right = new SumTask(mid + 1, end);
left.fork(); // 异步执行左任务
long rightResult = right.compute(); // 当前线程算右任务
long leftResult = left.join(); // 等待左任务
return leftResult + rightResult;
}
}Q17:什么是工作窃取(Work Stealing)?
答: 空闲线程从其他线程队列尾部窃取任务:
线程A: [任务1, 任务2, 任务3, 任务4] ← 从头部取
线程B: [任务5] ← 从A尾部偷任务4规则:
自己从头部取任务(LIFO)
窃取从尾部拿任务(FIFO)
减少竞争
优点:
充分利用 CPU
减少线程空闲
降低队列竞争
Q18:Fork/Join 和 ThreadPoolExecutor 有什么区别?
答:
// ThreadPoolExecutor:共享队列
ExecutorService pool = Executors.newFixedThreadPool(4);
// ForkJoinPool:工作窃取
ForkJoinPool fjp = new ForkJoinPool(4);Q19:Fork/Join 的 fork() 和 join() 有什么区别?
答:
fork():把任务提交到当前线程的队列,异步执行join():等待任务完成,返回结果
left.fork(); // 提交左任务
long rightResult = right.compute(); // 当前线程算右
long leftResult = left.join(); // 等左任务优化技巧:一个 fork,一个 compute,减少线程阻塞。
Q20:Fork/Join 的并行度如何设置?
答:
// 默认:CPU 核数
ForkJoinPool pool = new ForkJoinPool();
// 手动指定
ForkJoinPool pool = new ForkJoinPool(8);推荐:默认即可,除非有特殊需求。
注意:
并行度 ≠ 线程数,可能动态调整
阻塞任务会降低并行度
Q21:Fork/Join 的典型应用场景?
答:
并行流:
list.parallelStream()
.filter(...)
.map(...)
.collect(...);归并排序:
class MergeSortTask extends RecursiveAction { ... }数组求和:
class SumTask extends RecursiveTask<Long> { ... }树遍历:
class TreeTask extends RecursiveTask<Result> { ... }Q22:Fork/Join 的阈值如何设置?
答: 阈值决定任务拆分粒度:
private static final int THRESHOLD = 1000;
protected Long compute() {
if (end - start <= THRESHOLD) {
// 直接计算
} else {
// 拆分
}
}原则:
太小:拆分开销大
太大:并行度不够
经验值:1000 ~ 10000
Q23:Fork/Join 有什么缺点?
答:
编程复杂:需要拆分逻辑
调试困难:多线程栈
不适合 IO:阻塞会拖累
任务粒度敏感:阈值难调
异常处理复杂:需特殊处理
建议:CPU 密集型、可递归拆分任务才用。
三、CompletableFuture
Q24:什么是 CompletableFuture?
答: CompletableFuture 是 JDK 8 引入的异步编程工具:
实现
Future和CompletionStage支持链式调用
支持组合多个异步任务
支持回调,无需阻塞
CompletableFuture<String> future = CompletableFuture
.supplyAsync(() -> "Hello")
.thenApply(s -> s + " World")
.thenApply(String::toUpperCase);
System.out.println(future.get()); // HELLO WORLDQ25:CompletableFuture 和 Future 有什么区别?
答:
// Future:阻塞
Future<String> f = pool.submit(() -> "result");
String r = f.get(); // 阻塞
// CompletableFuture:回调
CompletableFuture.supplyAsync(() -> "result")
.thenAccept(System.out::println);Q26:CompletableFuture 的创建方式有哪些?
答:
// 1. 已完成
CompletableFuture<String> f1 =
CompletableFuture.completedFuture("done");
// 2. 无返回值,异步
CompletableFuture<Void> f2 =
CompletableFuture.runAsync(() -> doWork());
// 3. 有返回值,异步
CompletableFuture<String> f3 =
CompletableFuture.supplyAsync(() -> "result");
// 4. 指定线程池
CompletableFuture<String> f4 =
CompletableFuture.supplyAsync(() -> "result", executor);
// 5. 手动完成
CompletableFuture<String> f5 = new CompletableFuture<>();
f5.complete("manual");默认线程池:ForkJoinPool.commonPool()(CPU 核数 - 1)。
Q27:CompletableFuture 的常用方法有哪些?
答:
CompletableFuture.supplyAsync(() -> "Hello")
.thenApply(s -> s + " World") // 转换
.thenAccept(System.out::println) // 消费
.thenRun(() -> System.out.println("Done")); // 动作Q28:thenApply 和 thenCompose 有什么区别?
答:
thenApply:转换结果(同步函数)thenCompose:链式异步(返回 CompletableFuture)
// thenApply:Function<T, U>
CompletableFuture<String> f1 =
CompletableFuture.supplyAsync(() -> "Hello")
.thenApply(s -> s + " World");
// thenCompose:Function<T, CompletableFuture<U>>
CompletableFuture<String> f2 =
CompletableFuture.supplyAsync(() -> "Hello")
.thenCompose(s -> CompletableFuture.supplyAsync(
() -> s + " World"));区别:
thenApply 返回普通值
thenCompose 返回新的 CompletableFuture,避免嵌套
Q29:thenCombine 和 thenCompose 有什么区别?
答:
thenCombine:合并两个独立 CompletableFuturethenCompose:依赖前一个结果
// thenCombine:两个独立任务
CompletableFuture<String> f1 =
CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> f2 =
CompletableFuture.supplyAsync(() -> "World");
f1.thenCombine(f2, (a, b) -> a + " " + b)
.thenAccept(System.out::println); // Hello World
// thenCompose:依赖前一个
CompletableFuture.supplyAsync(() -> "user1")
.thenCompose(user -> queryOrder(user));Q30:CompletableFuture 如何处理异常?
答: 三种方式:
1. exceptionally:
CompletableFuture.supplyAsync(() -> {
throw new RuntimeException("error");
}).exceptionally(ex -> {
System.out.println("异常: " + ex);
return "默认值";
});2. handle:
CompletableFuture.supplyAsync(() -> "result")
.handle((result, ex) -> {
if (ex != null) return "异常处理";
return result;
});3. whenComplete:
CompletableFuture.supplyAsync(() -> "result")
.whenComplete((result, ex) -> {
if (ex != null) log.error("异常", ex);
else log.info("结果: " + result);
});区别:
exceptionally:只处理异常,返回默认值
handle:结果和异常都处理,有返回值
whenComplete:只做回调,不改变结果
Q31:allOf 和 anyOf 有什么区别?
答:
allOf:等所有完成anyOf:任一完成
// allOf
CompletableFuture<String> f1 = ...;
CompletableFuture<String> f2 = ...;
CompletableFuture<Void> all = CompletableFuture.allOf(f1, f2);
all.thenRun(() -> {
// f1、f2 都完成
String r1 = f1.join();
String r2 = f2.join();
});
// anyOf
CompletableFuture<Object> any = CompletableFuture.anyOf(f1, f2);
any.thenAccept(r -> System.out.println("最快: " + r));注意:allOf 返回 CompletableFuture<Void>,需手动 get 结果。
Q32:CompletableFuture 的 get() 和 join() 有什么区别?
答:
// get:需 try-catch
try {
String r = future.get();
} catch (Exception e) { }
// join:不需检查异常
String r = future.join();推荐:join() 更简洁。
Q33:CompletableFuture 默认线程池有什么问题?
答: 默认用 ForkJoinPool.commonPool(),问题:
共享:所有 CompletableFuture 共用
大小固定:CPU 核数 - 1
阻塞任务拖累:IO 任务会占满
无法监控:不好排查
解决:自定义线程池:
ExecutorService executor = new ThreadPoolExecutor(
10, 20, 60L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100),
new ThreadFactoryBuilder().setNameFormat("cf-pool-%d").build(),
new ThreadPoolExecutor.CallerRunsPolicy()
);
CompletableFuture.supplyAsync(() -> doWork(), executor);Q34:CompletableFuture 的 thenApply 和 thenApplyAsync 有什么区别?
答:
thenApply:当前线程执行thenApplyAsync:线程池执行
CompletableFuture.supplyAsync(() -> "Hello")
.thenApply(s -> { // 可能由完成 supplyAsync 的线程执行
return s + " World";
})
.thenApplyAsync(s -> { // 提交到线程池
return s + "!";
}, executor);建议:
简单转换用
thenApply耗时操作用
thenApplyAsync+ 自定义线程池
Q35:如何用 CompletableFuture 实现多个任务并行?
答:
// 三个独立任务并行
CompletableFuture<String> f1 =
CompletableFuture.supplyAsync(() -> queryUser(), executor);
CompletableFuture<String> f2 =
CompletableFuture.supplyAsync(() -> queryOrder(), executor);
CompletableFuture<String> f3 =
CompletableFuture.supplyAsync(() -> queryCoupon(), executor);
// 等所有完成
CompletableFuture<Void> all =
CompletableFuture.allOf(f1, f2, f3);
// 合并结果
CompletableFuture<Result> result = all.thenApply(v -> {
return new Result(f1.join(), f2.join(), f3.join());
});
result.thenAccept(r -> System.out.println(r));优势:三个查询并行,总耗时 = max(三个),而非 sum。
Q36:CompletableFuture 能替代线程池吗?
答: 不能,它们是互补关系:
线程池:管理线程,执行任务
CompletableFuture:编排异步任务,链式组合
// 线程池:执行
ExecutorService pool = Executors.newFixedThreadPool(10);
// CompletableFuture:编排
CompletableFuture.supplyAsync(() -> task1(), pool)
.thenCompose(r -> CompletableFuture.supplyAsync(
() -> task2(r), pool))
.thenAccept(System.out::println);最佳实践:CompletableFuture + 自定义线程池。
Q37:CompletableFuture 的常见坑有哪些?
答:
默认线程池阻塞:IO 任务用 commonPool
异常被吞:未处理时静默失败
get() 阻塞:失去异步意义
嵌套过深:可读性差
线程池未关闭:资源泄漏
// 坑:异常被吞
CompletableFuture.supplyAsync(() -> {
throw new RuntimeException("error");
}); // 无异常处理,静默失败
// 正确
.exceptionally(ex -> { log.error("", ex); return null; });Q38:CompletableFuture 的 thenAcceptBoth 和 thenCombine 有什么区别?
答:
thenAcceptBoth:消费两个结果,无返回值thenCombine:合并两个结果,有返回值
// thenAcceptBoth
f1.thenAcceptBoth(f2, (a, b) ->
System.out.println(a + b));
// thenCombine
CompletableFuture<String> combined =
f1.thenCombine(f2, (a, b) -> a + b);Q39:CompletableFuture 如何实现超时控制?
答: JDK 9+ 用 orTimeout() / completeOnTimeout():
// 超时抛异常
CompletableFuture.supplyAsync(() -> slowTask())
.orTimeout(3, TimeUnit.SECONDS)
.exceptionally(ex -> "超时默认值");
// 超时返回默认值
CompletableFuture.supplyAsync(() -> slowTask())
.completeOnTimeout("默认值", 3, TimeUnit.SECONDS);JDK 8:用 ScheduledExecutorService 手动实现:
ScheduledExecutorService scheduler = ...;
scheduler.schedule(() -> future.complete("超时"),
3, TimeUnit.SECONDS);Q40:CompletableFuture 的典型应用场景?
答:
并行调用多个服务:
CompletableFuture<User> user =
CompletableFuture.supplyAsync(() -> userService.get(id));
CompletableFuture<Order> order =
CompletableFuture.supplyAsync(() -> orderService.get(id));
CompletableFuture.allOf(user, order).join();异步流水线:
CompletableFuture.supplyAsync(() -> fetch())
.thenApply(data -> transform(data))
.thenAccept(result -> save(result));超时降级:
future.orTimeout(1, TimeUnit.SECONDS)
.exceptionally(ex -> fallback());组合多个结果:
f1.thenCombine(f2, (a, b) -> a + b)
.thenCombine(f3, (ab, c) -> ab + c);四、综合对比
Q41:ThreadLocal、Fork/Join、CompletableFuture 的定位?
答:
Q42:三者能配合使用吗?
答: 能:
// ThreadLocal 传递上下文
private static final ThreadLocal<String> TRACE = new ThreadLocal<>();
// CompletableFuture 异步编排
CompletableFuture.supplyAsync(() -> {
TRACE.set("trace-1");
return doWork();
}, executor)
.thenApplyAsync(r -> {
// 注意:线程池中 ThreadLocal 不自动传递
return r + TRACE.get();
}, executor)
.whenComplete((r, ex) -> TRACE.remove());注意:CompletableFuture 切换线程时,ThreadLocal 不传递,需用 TTL。
Q43:如何选择异步编程工具?
答:
Q44:ThreadLocal 在 CompletableFuture 中如何传递?
答: 用 TransmittableThreadLocal:
TransmittableThreadLocal<String> tl = new TransmittableThreadLocal<>();
tl.set("trace-1");
ExecutorService pool = TtlExecutors.getTtlExecutorService(
Executors.newFixedThreadPool(3));
CompletableFuture.supplyAsync(() -> {
return tl.get(); // 正确获取 trace-1
}, pool);原因:普通 ThreadLocal 在线程池切换时丢失。
Q45:Fork/Join 和 parallelStream 有什么关系?
答: parallelStream 底层用 ForkJoinPool.commonPool():
list.parallelStream()
.filter(...)
.map(...)
.collect(...);
// 底层:ForkJoinPool.commonPool()注意:
共享 commonPool,可能互相影响
可用自定义 ForkJoinPool:
ForkJoinPool pool = new ForkJoinPool(8);
pool.submit(() -> list.parallelStream()...).get();
评论