这里是全站文章的归档入口,下面是按时间倒序排列的全部文章。若想按主题浏览,请从首页的专题分类进入。
《Java 并发编程·线程与内存模型》13.2 异常、超时与取消
exceptionally、handle、whenComplete 的差别在于「谁能改变结果」,而不是「谁会被调用」。本节实测 orTimeout 在 200ms 预算下于 203–208ms 触发,底层任务照常跑完且未被中断;cancel(true) 同样不中断。三路通知的降级链整体 217ms,而不是串行至少 860ms。
posts
这里是全站文章的归档入口,下面是按时间倒序排列的全部文章。若想按主题浏览,请从首页的专题分类进入。
exceptionally、handle、whenComplete 的差别在于「谁能改变结果」,而不是「谁会被调用」。本节实测 orTimeout 在 200ms 预算下于 203–208ms 触发,底层任务照常跑完且未被中断;cancel(true) 同样不中断。三路通知的降级链整体 217ms,而不是串行至少 860ms。
Future 只能阻塞取值,CompletableFuture 把「结果还没到」变成一个可以继续挂步骤的对象。本节实测默认执行器是 parallelism=9 的 commonPool,thenApply 在完成者线程上执行,三路下游扇出从串行 290ms 降到 120ms,并给出 allOf 不 fail-fast 的真实证据。
阈值同时决定任务数和叶子成本:切太小,任务对象与调度开销吃掉收益;切太大,叶子里的插入排序退化成平方复杂度。本机 100 万元素归并排序实测,阈值 1 是 140ms、16 是 21ms、65536 是 1629ms,顺序版 105ms。文末用实测输出说明异常如何跨线程包装,以及 cancel 为什么停不下正在跑的叶子。
parallel() 只多了一个方法调用,却把用哪个池、拆多细、有没有副作用三件事都藏了起来。本机实测:同样 20 个 10ms 任务,commonPool 空闲时 31ms,被阻塞任务占满后 276ms;1000 个元素的并行流比顺序流慢 2.49 倍;36 个阻塞任务在 commonPool 上 1231ms,换成 16 线程的自定义池 916ms。
线程池是任务等线程,ForkJoinPool 是任务找线程:每个 worker 一个双向队列,自己从头部取最新的任务,空闲线程从尾部偷最老的任务。本机 10 核实测 commonPool 并行度 9,1 亿整数求和在四轮里从顺序 67ms 降到 13ms,池自己记录到 189 次窃取;并行度改成 3 后是 17ms。
固定频率和固定延迟的分母不同:前者对齐计划时刻,后者从执行结束开始算。本机实测同一个 200ms 周期、80ms 耗时的任务,fixedRate 六次的落后量稳定在 1–9ms,fixedDelay 则从 82ms 一路涨到 444ms;单次执行超过周期时,fixedRate 不丢弃欠账而是背靠背补跑。文末给出幂等键与补偿的落地写法。
延迟任务有两种组织方式:堆,或者时间轮。本文拆开 DelayQueue 的优先级队列与 leader 线程,实测乱序入队仍按剩余延迟出队、指数退避重试在 +0/+60/+173ms 完成;再手写一个 8 槽 50ms 的时间轮,对比同组任务的量化误差:时间轮平均 30ms,DelayQueue 平均 2.2ms。
Timer 只有一条线程、一个异常就能让它彻底停摆,还把相对延迟换算成墙上时钟的绝对时刻。三个实验对照:慢任务把后续任务从 50ms 推到 317ms;抛异常后 Timer 线程死亡、700ms 内只跑了 1 次,而 STPE 里健康任务照跑 8 次;字节码显示两者分别取 currentTimeMillis 与 nanoTime。
线程池上线之后要回答三个运维问题:指标怎么读、参数能不能改、进程怎么退。本节逐个核对 getActiveCount、队列长度、getCompletedTaskCount 的口径与误差,实测 setCorePoolSize 上调立即补线程、下调却要等 keepAlive 才收敛,并对比 shutdown 与 shutdownNow 的返回值和中断行为。
队列满、线程到顶之后发生什么,取决于拒绝策略。本节实测四种 JDK 拒绝策略在同一场景下的不同结果,解释 CallerRunsPolicy 如何在调用线程上形成反压、它在什么条件下会失效,并用无界队列实验证明 max 参数如何被架空,最后给出有界队列容量的推导方法与可记录指标的自定义拒绝策略。
《群岛纪元》的工作室用脚本刷矿、在交易所拉价,等运营发现时银币已经扩散。本节把经济风控拆成来源暴增、价格异常、集中转移三类可观测信号,说明价格指数的用途与局限、处置阶梯和误伤申诉,并给出基于滑动窗口、可完整运行的 Go 告警包与阈值标定口径。
从两个客户端同时发消息却看到不同顺序的事故写起,说明单聊会话 ID 如何由双方 ID 排序后哈希生成,比较会话内序号与全局序号两种方案,给出消息校验、按发送时间与 ID 分配序号的 Go 实现、连续序号测试和真实运行输出,并列出消息模型设计的常见错误。
四个品牌共用一套组件库,一次全量合并后,A 品牌的结算主色出现在 B 品牌的收银台,排查发现是品牌包被当成全局配置加载。多品牌复用的难点不是覆盖,而是边界与隔离。本节用 TypeScript 模块演示覆盖顺序、业务包与品牌包的权限边界、同层冲突检测和复用度计算,并给出多品牌接入前的验收判据。
把 ThreadPoolExecutor 的七个参数逐个拆开,说明 core、max、queue、keepAlive、工厂与拒绝策略各管哪一段,用三段判断还原 execute 的执行顺序,并以逐步提交实验打印 poolSize 与 queue 的真实变化,验证核心线程默认不超时、只有开启 allowCoreThreadTimeOut 才会归零。
读多写少的缓存该用什么锁:ReentrantReadWriteLock 让读并行、写互斥,StampedLock 再用版本戳把读变成乐观尝试。三版缓存代码对照,附读写并行、乐观读失效重试、不可重入、writeLock 不响应中断的本机实测输出。
ReentrantLock 比 synchronized 多出的四件事——可中断、超时、公平、多条件队列,逐项给出本机实测;再用 JFR 验证 JDK 25 默认配置下 synchronized 已不 pin 虚拟线程(LockingMode=1 可复现 20 次 pin),据此重新回答什么时候仍该坚持 synchronized。
从自旋锁的 CPU 代价讲到 LockSupport.park/unpark 的许可语义:许可最多一个、先 unpark 后 park 也生效、中断不清标志、虚假唤醒为何必须循环检查。附本机线程 dump 与 park 边界情形的真实输出,并给出借阅服务的唤醒式 worker。
从 Striped64 的 base + Cell[] 结构讲清 LongAdder 为什么能绕开单变量 CAS 的天花板:给出 JDK 25 的 add/sum 源码、cell 分片的实测形状、@Contended 与伪共享的真实编译约束,并在负载 155 的机器上如实记录 JMH 误差超过 50% 的数据不可引用。
把 java.util.concurrent.atomic 拆成基本、引用、数组、字段更新器四族:给出 JDK 25 的 getAndUpdate 源码与返回值语义、字段更新器省掉一个对象的代价与四条硬约束、实测的四种构造失败消息,并厘清 weakCompareAndSet 在 JDK 25 已被弃用为 plain 语义这件事。
从 compareAndSet 的 volatile 读写语义讲到失败重试的真实开销:用门闩固定交错,在无锁栈上把 ABA 复现成 2000/2000 次可重复的损坏,再用 AtomicStampedReference 压到 0/2000,并给出 AtomicMarkableReference 的 attemptMark 陷阱与本机实测输出。