怎么用这份速查
这份附录不解释原理,只解决一个问题:写代码时想不起签名。每一行给出「有哪几个方法、哪个是关键、边界在哪」,原理回到对应章节。
三条阅读约定:方法名省略类名但保留参数类型,签名以 JDK 25 为准,Thread.getId() 这类已弃用方法不收录;「边界」一列只写最容易踩的那一点,不是完整说明;标 P 的是 preview API,编译和运行都要 --enable-preview,其余都是稳定 API。
全书只有 StructuredTaskScope 一个 preview API。ScopedValue 在 JDK 25 已经转正,可以按稳定 API 写。
线程与执行
创建与属性
| 目的 | 方法 | 边界 |
|---|---|---|
| 建平台线程 | Thread.ofPlatform().name("w").start(r) | 返回 Thread,可直接 join() |
| 建虚拟线程 | Thread.ofVirtual().name("v").start(r) | 同上;isVirtual() 为 true |
| 一次性虚拟线程 | Thread.startVirtualThread(r) | 等价于 ofVirtual().start(r),拿不到 Thread 引用 |
| 当前线程 | Thread.currentThread() | 虚拟线程里调用同样合法 |
| 标识 | threadId()、getName() | getId() 已弃用,不要再用 |
| 类型判断 | isVirtual()、isDaemon() | 虚线程始终是 daemon |
| 状态 | state() | 返回 Thread.State 枚举,共六种 |
| 睡眠 | Thread.sleep(Duration) | 抛 InterruptedException,且会清除中断标志 |
| 让出 | Thread.yield()、Thread.onSpinWait() | 只是提示,不保证任何调度行为;dumpStack() 只打当前线程栈 |
中断与协作
| 方法 | 作用 | 是否清除标志 |
|---|---|---|
interrupt() | 设置中断标志;若线程阻塞在可中断点则抛异常 | 抛异常时清除 |
isInterrupted() | 查标志,不清除 | 否 |
Thread.interrupted() | 查并清除当前线程标志 | 是 |
join() / join(Duration) | 等待结束;被中断时抛异常 | 抛异常时清除 |
Thread.interrupted() 的清除行为是最常见的坑:在 catch 块里写 if (Thread.interrupted()) 会把标志吃掉,后续代码再也看不到中断。恢复中断用 Thread.currentThread().interrupt()。
执行器
| 工厂方法 | 核心行为 | 队列 | 什么时候用 |
|---|---|---|---|
newFixedThreadPool(n) | 固定 n 线程 | LinkedBlockingQueue(无界) | 负载平稳、任务不堆积 |
newCachedThreadPool() | 按需扩容,空闲 60s 回收 | SynchronousQueue | 短任务突发 |
newSingleThreadExecutor() | 单线程 + 无界队列 | LinkedBlockingQueue | 需要严格串行 |
newScheduledThreadPool(n) | 定时与周期 | DelayedWorkQueue | 定时任务 |
newVirtualThreadPerTaskExecutor() | 每任务一个虚拟线程 | 无 | IO 密集型、任务量大 |
前三个工厂的无界队列是生产事故的常见来源:任务提交速度持续高于处理速度时,队列会一直涨到 OOM。需要背压就用 new ThreadPoolExecutor(...) 显式指定有界队列和拒绝策略。
ThreadPoolExecutor 七参数
| 参数 | 含义 | 调参要点 |
|---|---|---|
corePoolSize | 常驻线程数 | 小于实际并发会让任务排进队列而不是扩线程 |
maximumPoolSize | 上限 | 只有队列满才会扩到这个值 |
keepAliveTime + unit | 非核心线程空闲存活 | 缓存型池靠它回收 |
workQueue | 等待队列 | 有界才能触发扩容与拒绝 |
threadFactory | 线程创建 | 命名便于 jstack 定位 |
handler | 拒绝策略 | CallerRunsPolicy 提供天然反压 |
执行顺序是:核心线程未满则新建 → 否则入队 → 队列满则扩容到 max → 仍满则拒绝。很多「池不扩容」的问题,根因是队列无界,永远走不到第三步。
提交与关闭
| 方法 | 返回 | 异常去哪 |
|---|---|---|
execute(Runnable) | 无 | 直接抛给线程的未捕获异常处理器 |
submit(Runnable) | Future<?> | 包在 Future 里,不 get() 就静默丢失 |
submit(Callable<T>) | Future<T> | 同上 |
invokeAll(tasks) | List<Future<T>> | 全部完成后返回 |
invokeAny(tasks) | T | 第一个成功的结果,其余取消 |
shutdown() | 无 | 不再收新任务,跑完队列 |
shutdownNow() | List<Runnable> | 中断线程,返回未执行的任务 |
awaitTermination(t, unit) | boolean | 超时返回 false |
submit 吞异常是本卷反复出现的问题:返回的 Future 不调用 get(),任务里抛出的异常就永远不会被任何人看到。
定时任务
| 方法 | 语义 | 漂移 |
|---|---|---|
schedule(cmd, delay, unit) | 一次性延迟 | 不涉及 |
scheduleAtFixedRate(cmd, init, period, unit) | 按开始时刻算周期 | 执行超时会累积追赶 |
scheduleWithFixedDelay(cmd, init, delay, unit) | 按结束时刻算间隔 | 不追赶,整体后移 |
任务抛异常会取消后续所有执行,且不打印任何东西。11.1 节有实测。
同步器
锁
| 类型 | 关键方法 | 边界 |
|---|---|---|
ReentrantLock | lock()、unlock()、tryLock(timeout, unit)、newCondition() | 必须在 finally 里 unlock() |
ReentrantReadWriteLock | readLock()、writeLock() | 不支持锁升级;写锁可降级为读锁 |
StampedLock | readLock()、writeLock()、tryOptimisticRead()、validate(stamp) | 不可重入;乐观读期间不能写 |
StampedLock 的乐观读不阻塞写线程,适合「读多、读到的值可以重算」的缓存场景:
final class StockCache {
private final StampedLock lock = new StampedLock();
private long stock;
long read() {
long stamp = lock.tryOptimisticRead();
long value = stock;
if (lock.validate(stamp)) {
return value;
}
stamp = lock.readLock();
try {
return stock;
} finally {
lock.unlockRead(stamp);
}
}
void write(long next) {
long stamp = lock.writeLock();
try {
stock = next;
} finally {
lock.unlockWrite(stamp);
}
}
}
乐观读返回的 stamp 必须交给 validate 校验,忘了这一步就等于读到脏值。
条件队列
| 方法 | 语义 |
|---|---|
await() | 释放锁并阻塞,被唤醒后重新获取锁 |
await(timeout, unit) | 超时返回 false |
signal() | 唤醒一个等待者,不释放锁 |
signalAll() | 唤醒全部,不释放锁 |
await 必须放在 while (!condition) 循环里,理由见 5.2 节。Condition 相比 Object.wait 的唯一实质优势是能按条件分组,避免 notifyAll 唤醒无关线程。
协作工具
| 工具 | 用途 | 关键方法 |
|---|---|---|
Semaphore | 限流、控制并发数 | acquire()、release()、tryAcquire(timeout, unit) |
CountDownLatch | 等 N 个事件,一次性 | countDown()、await() |
CyclicBarrier | 多轮同步点,可复用 | await()、reset() |
Phaser | 可增删参与者的多轮同步 | register()、arriveAndAwaitAdvance()、arriveAndDeregister() |
Exchanger | 两线程交换数据 | exchange(v) |
CountDownLatch 的计数归零后不能重置,需要重复使用就选 CyclicBarrier 或 Phaser。Semaphore 的 release() 可以超出许可数,写错会悄悄放宽限流,因此 acquire() 必须放在 try 外面:放在里面时 acquire 抛异常,finally 仍会 release(),许可数就会凭空增加。
并发容器
Map
| 方法 | 语义 | 注意 |
|---|---|---|
putIfAbsent(k, v) | 不存在才放 | 返回旧值,null 表示放过 |
computeIfAbsent(k, f) | 不存在才计算并放入 | 映射函数里不要做耗时操作,会占住 bin 锁 |
compute(k, f) | 原子重算 | 返回 null 会删除该键 |
merge(k, v, f) | 存在则合并 | 适合计数 |
replace(k, old, new) | 条件替换 | 返回是否成功 |
ConcurrentHashMap 不允许 null 键和 null 值,这一点与 HashMap 不同。computeIfAbsent 的映射函数如果递归修改同一个 map,会死锁或抛 IllegalStateException。
队列
| 队列 | 有界 | 阻塞 | 典型用途 |
|---|---|---|---|
ArrayBlockingQueue | 是 | 是 | 生产者消费者,内存可控 |
LinkedBlockingQueue | 可选 | 是 | 默认无界,慎用 |
SynchronousQueue | 容量 0 | 是 | 直接移交,CachedThreadPool 用它 |
PriorityBlockingQueue | 否 | 是 | 按优先级出队,无界 |
DelayQueue | 否 | 是 | 延迟重试、定时过期 |
LinkedTransferQueue | 否 | 部分 | transfer() 等消费者接手 |
四个核心方法的分工:
| 方法 | 队列满/空时 | 是否阻塞 |
|---|---|---|
add / remove | 抛异常 | 否 |
offer / poll | 返回 false / null | 否 |
offer(t, unit) / poll(t, unit) | 超时后返回 false / null | 有限阻塞 |
put / take | 一直等 | 是 |
DelayQueue 的元素必须实现 Delayed,getDelay 返回剩余时间。关闭队列时不要只置一个 volatile boolean 标志,阻塞在 take() 的消费者不会被唤醒,要投递哨兵元素(5.3 节实测过这个死锁)。
其他容器
CopyOnWriteArrayList 读无锁、写持锁并全量复制数组,只适合「读远多于写、集合小」;它的迭代器是快照,迭代期间的修改看不到,也不会抛 ConcurrentModificationException,元素上千且写入频繁时开销无法接受。ConcurrentLinkedQueue 读写都无锁,但 size() 是 O(n)。ConcurrentSkipListMap / ConcurrentSkipListSet 读写都是对数复杂度,需要有序遍历或有序去重时才用。
原子类与字段更新器
基本原子类
| 方法族 | 返回 | 语义 |
|---|---|---|
get() / set(v) | 值 / 无 | 普通 volatile 读写 |
getAndIncrement() / incrementAndGet() | 旧值 / 新值 | 自增 |
getAndAdd(d) / addAndGet(d) | 旧值 / 新值 | 加 d |
compareAndSet(expect, update) | boolean | CAS,失败返回 false |
getAndUpdate(f) / updateAndGet(f) | 旧值 / 新值 | 函数式 CAS 循环 |
getAndAccumulate(x, f) / accumulateAndGet(x, f) | 旧值 / 新值 | 带参数的函数式 CAS |
weakCompareAndSetPlain 等 | boolean | 弱内存序,允许虚假失败 |
weakCompareAndSet 系列从 JDK 9 起被标为弃用,JDK 25 上调用会有弃用警告。需要 CAS 就用 compareAndSet。
引用与标记
| 类 | 额外信息 | 解决什么 |
|---|---|---|
AtomicReference<T> | 无 | 对象引用的原子替换 |
AtomicStampedReference<T> | int 版本号 | ABA |
AtomicMarkableReference<T> | boolean 标记 | 只关心「改过没有」 |
AtomicStampedReference 的 compareAndSet 是四参数:期望引用、新引用、期望戳、新戳。只传引用不传戳的写法在 JDK 25 上不存在,写错会编译失败。
累加器
| 类 | 适用 | 边界 |
|---|---|---|
LongAdder | 高竞争计数 | sum() 不是快照,并发下不精确 |
LongAccumulator | 自定义结合运算 | 必须满足结合律与交换律 |
DoubleAdder | 高竞争浮点累加 | 结果有浮点误差 |
LongAdder 在低竞争下比 AtomicLong 慢,因为要多一次 Cell 定位。它适合「写多读少」的统计计数,不适合需要读到精确当前值的场景(如库存)。
字段更新器与 VarHandle
| 工具 | 用途 | 前提 |
|---|---|---|
AtomicIntegerFieldUpdater.newUpdater(C.class, "f") | 给已有类的字段加原子操作 | 字段必须是 volatile、非 static、可访问 |
AtomicReferenceFieldUpdater.newUpdater(C.class, T.class, "f") | 同上,引用类型 | 同上 |
VarHandle | 统一的内存访问与屏障语义 | 通过 MethodHandles.lookup() 获取 |
字段更新器不满足前提时会抛 IllegalArgumentException,消息分别是 Must be volatile type、Must be integer type。这些报错在 8.2 节有实测输出。
VarHandle 的内存序方法对照:
| 方法 | 语义 |
|---|---|
get / set | 普通读写,允许重排 |
getVolatile / setVolatile | 与 volatile 等价 |
getAcquire / setRelease | 单向屏障,开销更小 |
compareAndSet | 全屏障 |
compareAndExchange | 失败时也能拿到当前值 |
虚拟线程与结构化并发
虚拟线程
| 操作 | 写法 | 注意 |
|---|---|---|
| 创建 | Thread.ofVirtual().name("v-").start(r) | 不要池化 |
| 执行器 | Executors.newVirtualThreadPerTaskExecutor() | 每任务一个虚拟线程 |
| 判断 | Thread.currentThread().isVirtual() | 用于分支逻辑 |
| 限流 | new Semaphore(n) | 虚拟线程数量本身不是瓶颈,下游才是 |
虚拟线程不需要池化。用 newFixedThreadPool 包一层,等于把 M:N 调度退化成 M:1,白白丢掉它的全部优势。
ScopedValue(已转正)
| 方法 | 语义 |
|---|---|
ScopedValue.newInstance() | 创建未绑定的作用域值 |
carrier.run(() -> ...) | 在绑定下执行,无返回值 |
carrier.call(() -> v) | 在绑定下执行,有返回值 |
ScopedValue.where(k, v) | 构造绑定,可链式叠加 |
get() | 读当前绑定,未绑定抛 NoSuchElementException |
orElse(fallback) | 未绑定返回兜底值 |
orElseThrow(supplier) | 未绑定抛指定异常 |
isBound() | 是否已绑定 |
绑定不可变,出了 run / call 自动解除,普通线程不会继承。它不替代互斥,只替代「隐式传参」那一半的 ThreadLocal。
结构化并发(preview)
| 方法 | 语义 |
|---|---|
StructuredTaskScope.open() | 默认 Joiner,等全部结束 |
StructuredTaskScope.open(Joiner) | 指定失败与结果策略 |
scope.fork(Callable) | 启动子任务,返回 Subtask |
scope.join() | 等所有子任务结束 |
scope.close() | 关闭作用域,未结束的任务被取消 |
joiner.awaitAll() | 等全部,不因失败提前返回 |
joiner.allSuccessfulOrThrow() | 任一失败则取消其余并抛异常 |
joiner.anySuccessfulResultOrThrow() | 第一个成功即返回,取消其余 |
joiner.awaitAllSuccessfulOrThrow() | 等全部成功,任一失败则抛 |
joiner.allUntil(Predicate) | 自定义终止条件 |
ShutdownOnFailure 和 ShutdownOnSuccess 在 JDK 25 已经不存在,网上大量示例仍在用它们。JDK 25 的写法是 open(Joiner.allSuccessfulOrThrow()) 这类静态工厂。
Reservation reserve(String isbn, String memberId) throws Exception {
try (var scope = StructuredTaskScope.open(Joiner.allSuccessfulOrThrow())) {
var stock = scope.fork(() -> bookRepository.findByIsbn(isbn));
var quota = scope.fork(() -> quotaOf(memberId));
scope.join();
return decide(stock.get(), quota.get());
}
}
这段代码必须用 --enable-preview 编译和运行,且 close() 会取消所有未完成的子任务。
方法命名规律
| 前缀/后缀 | 含义 | 例子 |
|---|---|---|
getAndX | 返回旧值 | getAndIncrement、getAndSet |
XAndGet | 返回新值 | incrementAndGet、addAndGet |
tryX | 非阻塞,返回 boolean 或可空值 | tryLock、tryAcquire、tryOptimisticRead |
XOrThrow | 失败抛异常而非返回空 | allSuccessfulOrThrow |
put / take | 阻塞直到成功 | ArrayBlockingQueue |
offer / poll | 不阻塞或限时阻塞 | 同上 |
submit / execute | 前者返回 Future,后者返回 void | submit 的异常被封在 Future 里 |
最容易混的是 getAndX 与 XAndGet:自增并返回旧值还是新值,决定了计数从 0 开始还是从 1 开始,差一位的 bug 往往就出在这里。
我要做什么 → 用哪个
| 需求 | 首选 | 备选与说明 |
|---|---|---|
| 后台跑一个任务,要结果 | CompletableFuture.supplyAsync | ExecutorService.submit 也可,但要记得 get() |
| 三路下游并发聚合 | CompletableFuture.allOf | 需要失败即取消用 StructuredTaskScope + allSuccessfulOrThrow |
| 控制对下游的并发数 | Semaphore | 不要用线程池大小兼职限流 |
| 等一批任务全部完成 | CountDownLatch | 一次性;多轮用 CyclicBarrier |
| 周期性任务 | ScheduledExecutorService | 不要用 Timer,单线程且异常即死 |
| 延迟重试 | DelayQueue | 元素实现 Delayed |
| 高竞争计数统计 | LongAdder | 需要精确值用 AtomicLong |
| 读多写少的缓存 | StampedLock 乐观读 | 不可重入,逻辑要简单 |
| 跨线程传请求上下文 | 显式参数或 ScopedValue | ThreadLocal 在池里会串值 |
| 优雅停机 | shutdown() + awaitTermination | shutdownNow() 只在超时后用 |
什么时候不要用(反查)
| API | 不要用的场景 | 替代 |
|---|---|---|
synchronized | 需要可中断、可超时、多条件队列 | ReentrantLock |
ReentrantLock | 临界区只有几条赋值 | synchronized,更简单且 JIT 可优化 |
volatile | 需要「读-改-写」原子 | 原子类或锁 |
AtomicLong | 极高竞争的统计计数 | LongAdder |
LongAdder | 需要读取精确当前值 | AtomicLong 或加锁 |
ThreadLocal | 线程池 + 异步链路传参 | 显式参数或 ScopedValue |
newFixedThreadPool | 任务可能无限堆积 | 有界队列 + 拒绝策略 |
| 虚拟线程池化 | 任何场景 | 每任务一个虚拟线程 |
parallelStream | 任务里有阻塞 IO | 普通流 + 显式线程池 |
Timer | 任何生产场景 | ScheduledExecutorService |
速查只列卷一覆盖的 API。AQS 队列、ConcurrentHashMap 的 bin 锁与扩容、Striped64 分片、CompletableFuture 的依赖触发顺序,源码在卷二展开;线程池指标与 jcmd/jstack/JFR 的线上用法在卷三展开,附录 B 给出命令与事件的实际输出。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。