《Java 并发编程·JUC 源码与容器》11.1 ctl 与 Worker 生命周期
线程池把两个控制量压进一个 int:高 3 位是 runState,低 29 位是 workerCount。本节用反射读出运行中线程池的真实 ctl 并逐位拆解,解释 RUNNING 为什么取负数、五个状态为何只能单向递增,以及 Worker 为何继承 AQS 用 tryAcquire 表示正在跑任务。
tag
线程池把两个控制量压进一个 int:高 3 位是 runState,低 29 位是 workerCount。本节用反射读出运行中线程池的真实 ctl 并逐位拆解,解释 RUNNING 为什么取负数、五个状态为何只能单向递增,以及 Worker 为何继承 AQS 用 tryAcquire 表示正在跑任务。
同一混合负载下用 JMH 对比 synchronized 栈、ReentrantLock 栈与 Treiber 栈,再用自写探针统计 CAS 失败次数与 p99/p999:4 线程时无锁吞吐只有有锁的五分之一,但 p999 反而低一个数量级。
把一次 pop 拆成读快照与提交两步,用单线程脚本稳定复现丢失更新;再拆 AtomicStampedReference 的 Pair 与 VarHandle PAIR,说明 Java 有 GC 为什么仍会逻辑 ABA,以及 AtomicMarkableReference 一个 bit 够用在哪、不够用在哪。
用 VarHandle 手写 Treiber 栈与 Michael-Scott 队列,实测一百万次入队中 tail 有一百零六万次不是真实末尾;再对照 ConcurrentLinkedQueue 的 MAX_HOPS、updateHead、succ 与 skipDeadNodes,说明它为什么要多一层帮助推进。
梳理 JDK 25 中 Unsafe 到 VarHandle 的迁移路径:实例字段、静态字段、数组元素与字节数组视图四种句柄,lookup 访问权限与 final 字段限制,给出一张迁移对照表和库存扣减的迁移前后代码,并说明哪些能力 VarHandle 接不住。
解释 getAcquire/setRelease 的单向屏障语义,用 JCStress 复现一个普通标志位的不安全发布并修好它,再用 Dekker 实验说明 acquire/release 为什么仍弱于 volatile,最后给出 fullFence 等五个屏障方法的适用边界。
从 JDK 25 源码拆解 VarHandle 的五种访问模式:plain、opaque、acquire/release、volatile 与 CAS 各自保证什么,用 JCStress 的 Dekker 实验展示 plain 与 volatile 的差距,并核对句柄获取与类型检查时机,最后落到库存扣减代码。
进入 LongAdder 的引擎 Striped64:探针哈希如何把线程映射到 Cell、cellsBusy 为什么是一把不阻塞的自旋锁、longAccumulate 的建槽/重试/扩容三分支,以及 JDK 25 用 SharedSecrets 桥接 ThreadLocalRandom 探针的原因与手写简化版实测。
拆 LongAdder 的 base + cells 双层结构:@Contended 为什么让每个 Cell 约占 289 字节,sum() 为何不是快照(本机 158 万次采样有 24 万次把在借数算成负数),reset() 的破坏性,以及高竞争实测下它何时反而不如 AtomicLong。
拆开 JDK 25 的 AtomicInteger:源码注释为何承认本该用 VarHandle 却仍走 jdk.internal.misc.Unsafe,getAndAddInt、putIntRelease、compareAndSetInt 各自的内存语义,weakCompareAndSet 与 compareAndSet 的实际差别,以及字段更新器的实现与本机字节码和重试数据。
从锁拆分、Node 对象开销、伪共享与容量语义四个角度解释三种阻塞队列的吞吐差异,给出本机 JMH 与简易探针的真实输出、JOL 字段偏移与保留集数据,说明为什么这些数字只能看数量级、不能横向引用,并用 libraryd-conc 的容量与监控工具收尾。
拆解 JDK 25 中已重构的 SynchronousQueue(Transferer 继承 LinkedTransferQueue)、PriorityBlockingQueue 的堆与 allocationSpinLock 扩容、DelayQueue 的 leader/available 协议,用零容量交接、同优先级出队、延迟到期三组实跑数据说明边界与代价。
从 JDK 25 源码对比 ArrayBlockingQueue 的单锁双条件与 LinkedBlockingQueue 的双锁双条件,解释 count 为何必须是 AtomicInteger、signalNotEmpty/signalNotFull 的精确唤醒判据,并用同构探针统计每次 put 的唤醒次数与 JMH 吞吐,给出 libraryd-conc 的有界事件队列落地与容量取舍。
把 ConcurrentHashMap、跳表、写时复制与阻塞队列放进有序性、读写比例、迭代语义、内存放大四个维度的矩阵,给出可直接照做的决策树与五种常见误选,并用 libraryd-conc 的 ISBN 索引改造实测每次更新的分配量从 162684 字节降到约 570 字节。
以 JDK 25 源码拆 ConcurrentSkipListMap:Index 三层指针为何取代了 HeadIndex 与 level 字段、doPut 如何先挂底层再补索引并重试,并用本机反射统计的层数、索引占比与读写数据回答为何有序并发容器选跳表。
以 JDK 25 源码拆开 CopyOnWriteArrayList 的写时复制:锁字段为何从 ReentrantLock 换成 Object 监视器、add 的复制路径、读为何无锁,并用本机分配计数给出每次写的字节数与 addIfAbsent 的比较次数。
用本机实验说明 ConcurrentHashMap 迭代器的弱一致性:Traverser 如何跨新旧表、为什么不抛 ConcurrentModificationException,以及 compute/computeIfAbsent/merge 只锁单桶的原子性边界和递归更新导致的异常与跨 map 死锁。
沿 JDK 25 源码拆开 ConcurrentHashMap 的 get 与 putVal:无锁读、桶首 CAS、synchronized 锁桶首、MOVED 转发与 helpTransfer,再用探针展示 baseCount、CounterCell 分片计数和 sumCount 的近似性,附本机分支日志。
从 JDK 25 的 javap 输出出发,逐个核对 ConcurrentHashMap 的 Node、TreeBin、ForwardingNode、ReservationNode 与 CounterCell 字段,解释 MOVED/TREEBIN/RESERVED 三种负 hash 与 sizeCtl 在初始化、阈值、扩容三种状态下的编码,并给出桶状态图和本机探针输出。
拆开 Exchanger 的 Slot、Participant 与 arena,说明它为什么不用 AQS、0 号槽为何是唯一的阻塞点、配对与超时怎样收尾,再用双缓冲快照和两个信号量反序获取的真实死锁输出,收束本章,并指出这类死锁的检测盲区与组合风险。