《Java 并发编程·线程与内存模型》附录 A 并发 API 速查

把 JDK 25 常用并发 API 按线程与执行、同步器、并发容器、原子类、虚拟线程与结构化并发五组整理成速查表,每条给出核心方法、语义要点与选择边界,并附方法命名规律和「我要做什么 → 用哪个」反查表,以及各 API 的「什么时候不要用」。

怎么用这份速查

这份附录不解释原理,只解决一个问题:写代码时想不起签名。每一行给出「有哪几个方法、哪个是关键、边界在哪」,原理回到对应章节。

三条阅读约定:方法名省略类名但保留参数类型,签名以 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 节有实测。

同步器

锁

类型关键方法边界
ReentrantLocklock()、unlock()、tryLock(timeout, unit)、newCondition()必须在 finally 里 unlock()
ReentrantReadWriteLockreadLock()、writeLock()不支持锁升级;写锁可降级为读锁
StampedLockreadLock()、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)booleanCAS,失败返回 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,后者返回 voidsubmit 的异常被封在 Future 里

最容易混的是 getAndX 与 XAndGet:自增并返回旧值还是新值,决定了计数从 0 开始还是从 1 开始,差一位的 bug 往往就出在这里。

我要做什么 → 用哪个

需求首选备选与说明
后台跑一个任务,要结果CompletableFuture.supplyAsyncExecutorService.submit 也可,但要记得 get()
三路下游并发聚合CompletableFuture.allOf需要失败即取消用 StructuredTaskScope + allSuccessfulOrThrow
控制对下游的并发数Semaphore不要用线程池大小兼职限流
等一批任务全部完成CountDownLatch一次性;多轮用 CyclicBarrier
周期性任务ScheduledExecutorService不要用 Timer,单线程且异常即死
延迟重试DelayQueue元素实现 Delayed
高竞争计数统计LongAdder需要精确值用 AtomicLong
读多写少的缓存StampedLock 乐观读不可重入,逻辑要简单
跨线程传请求上下文显式参数或 ScopedValueThreadLocal 在池里会串值
优雅停机shutdown() + awaitTerminationshutdownNow() 只在超时后用

什么时候不要用(反查)

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 给出命令与事件的实际输出。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「books」更多文章

  1. 《Java 消息中间件·事件驱动架构》附录 B 架构决策记录模板
  2. 《Java 消息中间件·事件驱动架构》附录 A 术语表
  3. 《Java 消息中间件·事件驱动架构》15.3 全书验收