跳到正文
Elaine Blog
返回

并发设计模式与确定性测试

并发编程

并发设计的首选策略是减少共享可变状态。能用不可变值传递、单线程所有权或消息队列解决时,不要先增加锁。必须共享时, 把不变量、同步边界和锁顺序写进设计并用确定时序测试。

Table of contents

Open Table of contents

从固定锁顺序开始

下载确定性转账样例下载, 运行:

java -cp target/classes dev.elaine.concurrency.DeterministicTransferSample

两个线程同时执行相反方向转账。代码始终按账户 ID 从小到大获取两把锁,因此不会形成 A 等 B、B 等 A 的循环等待;最终 输出 总金额=20000

四种减少共享的模式

模式核心约束适合场景
不可变快照创建后不修改配置、查询结果、事件
线程封闭状态只由一个线程访问事件循环、批次聚合
Actor 思想每个实体串行处理消息订单、会话、设备状态
分区所有权按键路由到固定分片高吞吐状态更新

Actor 不等于必须引入某个框架。单线程执行器加有界邮箱也能体现所有权,但要设计邮箱容量、失败监督、消息顺序和持久化。

不要用 sleep 构造时序

Thread.sleep(100) 不能证明另一个线程已到达目标位置,只会让测试在快机器上浪费时间、在慢机器上偶发失败。使用 CountDownLatch、Barrier、Phaser 或测试钩子明确控制“已读旧值”“允许继续”等事件。

var ready = new java.util.concurrent.CountDownLatch(2);
var start = new java.util.concurrent.CountDownLatch(1);
// 每个工作线程先 ready.countDown(),再 start.await()

所有等待都应有测试级超时。超时失败时打印线程转储,而不是让 CI 永久挂起。

测试业务不变量

不要断言线程输出顺序,除非顺序本身是契约。并发转账测试应断言总金额守恒、余额不为负、所有任务结束且没有未捕获异常。 对 CAS、队列和锁等底层算法,普通 JUnit 不足以覆盖所有内存交错,可使用 JCStress;性能结论使用 JMH。

线性化点与故障路径

为并发操作指出线性化点:整个操作在逻辑上瞬间生效的位置,例如成功 CAS 或持锁后的状态写入。再逐项测试中断、超时、 拒绝、重复消息、部分失败和关闭过程。

生产验证还需要指标与事件:队列深度、锁等待、任务年龄、拒绝、取消、超时和不变量告警。没有这些证据,并发错误只会表现 为偶发业务数据异常。

下一步

继续阅读网络、I/O 与 Java 服务器阶段,把线程模型、背压和取消协议 应用到 Socket、NIO、Netty 与 HTTP 服务。


分享这篇文章:

上一篇
虚拟线程、固定与观测
下一篇
一次网页请求经历了什么:从域名到应用线程