禅与计算机 禅与计算机
首页
  • Java基础

    • 聊一聊java一些核心知识点
    • 聊聊java面向对象核心知识点
    • 聊聊Java中的异常
    • 聊聊Java中的常用类String
    • 万字长文带你细聊Java注解本质
    • 来聊聊Java的反射机制
    • 深入解析Java泛型的魅力与机制
    • Java集合框架深度解析与面试指南
    • Java常用集合类HashMap深度解析
    • LinkedHashMap源码到面试题的全解析
    • 深入解析CopyOnWriteArrayList的工作机制
    • Java基础IO总结
    • Java三大IO模型小结
    • Java BIO NIO AIO详解
    • Java进阶NIO之IO多路复用详解
    • Java8流式编程入门
    • 一文速通lambda与函数式编程
    • Java8函数式方法引用最佳实践
  • Java并发编程

    • Java并发编程基础小结
    • 深入理解Java中的final关键字
    • 浅谈Java并发安全发布技术
    • 浅谈Java并发编程中断的哲学
    • Java线程池知识点小结
    • 浅谈Java线程池中拒绝策略与流控的艺术
    • synchronized关键字使用指南
    • 深入源码解析synchronized关键字
    • 详解JUC包下的锁
    • 详解并发编程中的CAS原子类
    • LongAdder源码分析
    • AQS源码解析
    • 深入剖析Java并发编程中的死锁问题
    • Java并发容器总结
    • 详解Java并发编程volatile关键字
    • 并发编程ThreadLocal必知必会
    • CompletableFuture基础实践小结
    • CompletableFuture异步多任务最佳实践
    • 硬核详解FutureTask设计与实现
    • 线程池大小设置的底层逻辑与场景化方案
    • 来聊一个有趣的限流器RateLimiter
  • JVM相关

    • 从零开始掌握 JVM
    • JVM核心知识点小结
    • JVM指令集概览:基础与应用
    • JVM类加载器深度解析
    • JVM方法区深度解析
    • Java内存模型JMM详解
    • Java对象大小的精确计算方法
    • 逃逸分析在Java中的应用与优化
    • 从零开始理解JVM的JIT编译机制
    • G1垃圾回收器:原理详解与调优指南
    • JVM故障排查实战指南
    • JVM内存问题排错最佳实践
    • JVM内存溢出排查指南
    • 简明的Arthas使用教程
    • 简明的Arthas配置及基础运维教程
    • 基于Arthas Idea的JVM故障排查与指令生成
    • 基于arthas量化监控诊断java应用方法论与实践
    • 深入剖析arthas技术原理
  • 深入理解Spring框架

    • Spring 核心知识点全面解析
    • Spring核心功能IOC详解
    • Spring AOP 深度剖析与实践
    • Spring 三级缓存机制深度解析
    • 深入 Spring 源码,剖析设计模式的落地实践
    • 探索 Spring 事务的奥秘
    • 深入解析Spring Bean的生命周期管理
    • 解读 Spring Boot 核心知识点
    • Spring Boot 启动优化实战:1分钟到13秒的排查与优化之路
    • Spring Boot自动装配原理及实践
    • 一文快速上手Sharding-JDBC
    • sharding-jdbc如何实现分页查询
    • 基于DynamicDataSource整合分库分表框架Shardingsphere
  • 计算机组成原理

    • 计算机硬件知识小结
    • CPU核心知识点小结
    • 浅谈CPU流水线的艺术
    • 从Java程序员视角聊聊CPU缓存
    • CPU任务调度和伪共享问题小结
    • CPU MESI缓存一致性协议
    • CPU内存管理机制
    • 内存深度解析
    • 磁盘存储原理
    • 详解计算机启动步骤
    • CPU南北桥架构与发展史
    • CPU中断机制与硬件交互详解
  • 操作系统

    • 如何实现一个高性能服务器
    • Linux文件结构与文件权限
    • Linux常见压缩指令小结
    • Linux核心系统调用详解
    • Linux进程管理
    • Linux线程管理
    • 进程与线程深度解析
    • Linux进程间通信机制
    • 零拷贝技术原理与实践
    • CPU缓存一致性问题深度解析
    • IO任务与CPU调度艺术
  • 计算机网络

    • 网卡通信原理详解
    • 网卡数据包处理指南
    • 基于抓包详解TCP协议
  • 编码最佳实践

    • 浅谈现代软件工程TDD最佳实践
    • 浅谈TDD模式下并发程序设计与实现
    • 面向AI编程新范式Trae后端开发环境搭建与实践
    • 基于提示词工程的Redis签到功能开发实践
    • 基于Vibe Coding的Redis分页查询实现
    • 告别AI无效对话:资深工程师的提示词设计最佳实践
  • 实用技巧与配置

    • Mac常用快捷键与效率插件指南
    • Keynote技术科普短视频制作全攻略
  • 写作

    • 写好技术博客的5大核心原则:从认知科学到AI工具的全流程指南
  • 开发工具

    • IDEA配置详解与高效使用指南
  • Nodejs
  • 博客搭建
  • Redis

    • Redis核心知识小结
    • 解锁Redis发布订阅模式
    • 掌握Redis事务
    • Redis主从复制技术
    • Redis的哨兵模式详解
    • 深度剖析Redisson分布式锁
    • 详解redis单线程设计思路
    • 来聊聊Redis所实现的Reactor模型
    • Redis RDB持久化源码深度解析
    • 来聊聊redis的AOF写入
    • 来聊聊Redis持久化AOF管道通信的设计
    • 来聊聊redis集群数据迁移
    • Redis SDS动态字符串深度解析
    • 高效索引的秘密:redis跳表设计与实现
    • 聊聊redis中的字典设计与实现
  • MySQL

    • MySQL基础知识点小结
    • 解读MySQL 索引基础
    • MySQL 索引进阶指南
    • 解读MySQL Explain关键字
    • 探秘 MySQL 锁:原理与实践
    • 详解MySQL重做日志redolog
    • 详解undoLog在MySQL MVCC中的运用
    • MySQL二进制日志binlog核心知识点
    • MySQL高效插入数据的最佳实践
    • MySQL分页查询优化指南
    • MySQL流式查询的奥秘与应用解析
    • 来聊聊分库分表
    • 来聊聊大厂常用的分布式ID生成方案
  • ElasticSearch

    • 从Lucene到Elasticsearch:进化之路
    • ES 基础使用指南
    • ElasticSearch如何写入一篇文档
    • 深入剖析Elasticsearch文档读取原理
    • 聊聊ElasticSearch性能调优
    • Spring借助Easy-Es操作ES
  • Netty

    • 一文快速了解高性能网络通信框架Netty
    • Netty网络传输简记
    • 来聊聊Netty的ByteBuf
    • 来聊聊Netty消息发送的那些事
    • 解密Netty高性能之谜:NioEventLoop线程池阻塞分析
    • 详解Netty中的责任链Pipeline如何管理ChannelHandler
    • Netty Reactor模型常见知识点小结
    • Netty如何驾驭TCP流式传输?粘包拆包问题全解
    • Netty解码器源码解析
  • 消息队列

    • 一文快速入门消息队列
    • 消息队列RocketMQ入门指南
    • 基于RocketMQ实现分布式事务
    • RocketMQ容器化最佳实践
    • RocketMQ常见问题与深度解析
    • Kafka快速安装与使用指南
  • Nginx

    • Linux下的nginx安装
    • Nginx基础入门总结
    • Nginx核心指令小结
    • Nginx进程结构与核心模块初探
    • Nginx应用进阶HTTP核心模块配置
    • Nginx缓存及HTTPS配置小记
    • nginx高可用实践简记
    • Nginx性能优化
  • 微服务基础

    • 微服务基础知识小结
    • 分布式事务核心概念小结
    • OpenFeign核心知识小结
    • 微服务组件Gateway核心使用小结
    • 分布式事务Seata实践
    • 用 Docker Compose 完成 Seata 的整合部署
  • Nacos

    • Nacos服务注册原理全解析
    • Nacos服务订阅流程全解析
    • Nacos服务变更推送流程全解析
    • 深入解析SpringCloud负载均衡器Loadbalancer
    • Nacos源码环境搭建与调试指南
  • Seata

    • 深度剖析Seata源码
  • Docker部署

    • 一文快速掌握docker的理念和基本使用
    • 使用docker编排容器
    • 基于docker-compose部署微服务基本环境
    • 基于docker容器化部署微服务
    • Gateway全局异常处理及请求响应监控
    • Docker图形化界面工具Portainer最佳实践
  • Go基础

    • 一文带你速通Go语言基础语法
    • 一文快速掌握Go语言切片
    • 来聊聊go语言的hashMap
    • 一文速通go语言类型系统
    • 浅谈Go语言中的面向对象
    • go语言是如何实现协程的
    • 聊聊go语言中的GMP模型
    • 极简的go语言channel入门
    • 聊聊go语言基于epoll的网络并发实现
    • 写给Java开发的Go语言协程实践
  • mini-redis实战

    • 来聊聊我用go手写redis这件事
    • mini-redis如何解析处理客户端请求
    • 实现mini-redis字符串操作
    • 硬核复刻redis底层双向链表核心实现
    • 动手复刻redis之go语言下的字典的设计与落地
    • Go 语言下的 Redis 跳表设计与实现
    • Go 语言版 Redis 有序集合指令复刻探索
  • 项目编排

    • Spring脚手架创建简记
    • Spring脚手架集成分页插件
    • Spring脚手架集成校验框架
    • maven父子模块两种搭建方式简记
    • SpringBoot+Vue3前后端快速整合入门
    • 来聊聊Java项目分层规范
  • 场景设计

    • Java实现文件分片上传
    • 基于时间缓存优化浏览器轮询阻塞问题
    • 基于EasyExcel实现高效导出
    • 10亿数据高效插入MySQL最佳方案
    • 从开源框架中学习那些实用的位运算技巧
  • CI/CD

    • 基于NETAPP实现内网穿透
    • 基于Gitee实现Jenkins自动化部署SpringBoot项目
    • Jenkins离线安装部署教程简记
    • 基于Nexus搭建Maven私服基础入门
    • 基于内网的Jenkins整合gitlab综合方案简记
  • 监控方法论

    • SpringBoot集成Prometheus与Grafana监控
    • Java监控度量Micrometer全解析
    • 从 micrometer计量器角度快速上手promQL
    • 硬核安利一个监控告警开源项目Nightingale
  • Spring AI

    • Spring AI Alibaba深度实战:一文掌握智能体开发全流程
    • Spring AI Alibaba实战:JVM监控诊断Arthas Agent的工程化构建与最佳实践
  • 大模型评测

    • M2.7 真能打!我用两个真实场景测了测,结果有点意外
    • Qoder JetBrains插件评测:祖传代码重构与接口优化实战
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

sharkchili

计算机禅修者
首页
  • Java基础

    • 聊一聊java一些核心知识点
    • 聊聊java面向对象核心知识点
    • 聊聊Java中的异常
    • 聊聊Java中的常用类String
    • 万字长文带你细聊Java注解本质
    • 来聊聊Java的反射机制
    • 深入解析Java泛型的魅力与机制
    • Java集合框架深度解析与面试指南
    • Java常用集合类HashMap深度解析
    • LinkedHashMap源码到面试题的全解析
    • 深入解析CopyOnWriteArrayList的工作机制
    • Java基础IO总结
    • Java三大IO模型小结
    • Java BIO NIO AIO详解
    • Java进阶NIO之IO多路复用详解
    • Java8流式编程入门
    • 一文速通lambda与函数式编程
    • Java8函数式方法引用最佳实践
  • Java并发编程

    • Java并发编程基础小结
    • 深入理解Java中的final关键字
    • 浅谈Java并发安全发布技术
    • 浅谈Java并发编程中断的哲学
    • Java线程池知识点小结
    • 浅谈Java线程池中拒绝策略与流控的艺术
    • synchronized关键字使用指南
    • 深入源码解析synchronized关键字
    • 详解JUC包下的锁
    • 详解并发编程中的CAS原子类
    • LongAdder源码分析
    • AQS源码解析
    • 深入剖析Java并发编程中的死锁问题
    • Java并发容器总结
    • 详解Java并发编程volatile关键字
    • 并发编程ThreadLocal必知必会
    • CompletableFuture基础实践小结
    • CompletableFuture异步多任务最佳实践
    • 硬核详解FutureTask设计与实现
    • 线程池大小设置的底层逻辑与场景化方案
    • 来聊一个有趣的限流器RateLimiter
  • JVM相关

    • 从零开始掌握 JVM
    • JVM核心知识点小结
    • JVM指令集概览:基础与应用
    • JVM类加载器深度解析
    • JVM方法区深度解析
    • Java内存模型JMM详解
    • Java对象大小的精确计算方法
    • 逃逸分析在Java中的应用与优化
    • 从零开始理解JVM的JIT编译机制
    • G1垃圾回收器:原理详解与调优指南
    • JVM故障排查实战指南
    • JVM内存问题排错最佳实践
    • JVM内存溢出排查指南
    • 简明的Arthas使用教程
    • 简明的Arthas配置及基础运维教程
    • 基于Arthas Idea的JVM故障排查与指令生成
    • 基于arthas量化监控诊断java应用方法论与实践
    • 深入剖析arthas技术原理
  • 深入理解Spring框架

    • Spring 核心知识点全面解析
    • Spring核心功能IOC详解
    • Spring AOP 深度剖析与实践
    • Spring 三级缓存机制深度解析
    • 深入 Spring 源码,剖析设计模式的落地实践
    • 探索 Spring 事务的奥秘
    • 深入解析Spring Bean的生命周期管理
    • 解读 Spring Boot 核心知识点
    • Spring Boot 启动优化实战:1分钟到13秒的排查与优化之路
    • Spring Boot自动装配原理及实践
    • 一文快速上手Sharding-JDBC
    • sharding-jdbc如何实现分页查询
    • 基于DynamicDataSource整合分库分表框架Shardingsphere
  • 计算机组成原理

    • 计算机硬件知识小结
    • CPU核心知识点小结
    • 浅谈CPU流水线的艺术
    • 从Java程序员视角聊聊CPU缓存
    • CPU任务调度和伪共享问题小结
    • CPU MESI缓存一致性协议
    • CPU内存管理机制
    • 内存深度解析
    • 磁盘存储原理
    • 详解计算机启动步骤
    • CPU南北桥架构与发展史
    • CPU中断机制与硬件交互详解
  • 操作系统

    • 如何实现一个高性能服务器
    • Linux文件结构与文件权限
    • Linux常见压缩指令小结
    • Linux核心系统调用详解
    • Linux进程管理
    • Linux线程管理
    • 进程与线程深度解析
    • Linux进程间通信机制
    • 零拷贝技术原理与实践
    • CPU缓存一致性问题深度解析
    • IO任务与CPU调度艺术
  • 计算机网络

    • 网卡通信原理详解
    • 网卡数据包处理指南
    • 基于抓包详解TCP协议
  • 编码最佳实践

    • 浅谈现代软件工程TDD最佳实践
    • 浅谈TDD模式下并发程序设计与实现
    • 面向AI编程新范式Trae后端开发环境搭建与实践
    • 基于提示词工程的Redis签到功能开发实践
    • 基于Vibe Coding的Redis分页查询实现
    • 告别AI无效对话:资深工程师的提示词设计最佳实践
  • 实用技巧与配置

    • Mac常用快捷键与效率插件指南
    • Keynote技术科普短视频制作全攻略
  • 写作

    • 写好技术博客的5大核心原则:从认知科学到AI工具的全流程指南
  • 开发工具

    • IDEA配置详解与高效使用指南
  • Nodejs
  • 博客搭建
  • Redis

    • Redis核心知识小结
    • 解锁Redis发布订阅模式
    • 掌握Redis事务
    • Redis主从复制技术
    • Redis的哨兵模式详解
    • 深度剖析Redisson分布式锁
    • 详解redis单线程设计思路
    • 来聊聊Redis所实现的Reactor模型
    • Redis RDB持久化源码深度解析
    • 来聊聊redis的AOF写入
    • 来聊聊Redis持久化AOF管道通信的设计
    • 来聊聊redis集群数据迁移
    • Redis SDS动态字符串深度解析
    • 高效索引的秘密:redis跳表设计与实现
    • 聊聊redis中的字典设计与实现
  • MySQL

    • MySQL基础知识点小结
    • 解读MySQL 索引基础
    • MySQL 索引进阶指南
    • 解读MySQL Explain关键字
    • 探秘 MySQL 锁:原理与实践
    • 详解MySQL重做日志redolog
    • 详解undoLog在MySQL MVCC中的运用
    • MySQL二进制日志binlog核心知识点
    • MySQL高效插入数据的最佳实践
    • MySQL分页查询优化指南
    • MySQL流式查询的奥秘与应用解析
    • 来聊聊分库分表
    • 来聊聊大厂常用的分布式ID生成方案
  • ElasticSearch

    • 从Lucene到Elasticsearch:进化之路
    • ES 基础使用指南
    • ElasticSearch如何写入一篇文档
    • 深入剖析Elasticsearch文档读取原理
    • 聊聊ElasticSearch性能调优
    • Spring借助Easy-Es操作ES
  • Netty

    • 一文快速了解高性能网络通信框架Netty
    • Netty网络传输简记
    • 来聊聊Netty的ByteBuf
    • 来聊聊Netty消息发送的那些事
    • 解密Netty高性能之谜:NioEventLoop线程池阻塞分析
    • 详解Netty中的责任链Pipeline如何管理ChannelHandler
    • Netty Reactor模型常见知识点小结
    • Netty如何驾驭TCP流式传输?粘包拆包问题全解
    • Netty解码器源码解析
  • 消息队列

    • 一文快速入门消息队列
    • 消息队列RocketMQ入门指南
    • 基于RocketMQ实现分布式事务
    • RocketMQ容器化最佳实践
    • RocketMQ常见问题与深度解析
    • Kafka快速安装与使用指南
  • Nginx

    • Linux下的nginx安装
    • Nginx基础入门总结
    • Nginx核心指令小结
    • Nginx进程结构与核心模块初探
    • Nginx应用进阶HTTP核心模块配置
    • Nginx缓存及HTTPS配置小记
    • nginx高可用实践简记
    • Nginx性能优化
  • 微服务基础

    • 微服务基础知识小结
    • 分布式事务核心概念小结
    • OpenFeign核心知识小结
    • 微服务组件Gateway核心使用小结
    • 分布式事务Seata实践
    • 用 Docker Compose 完成 Seata 的整合部署
  • Nacos

    • Nacos服务注册原理全解析
    • Nacos服务订阅流程全解析
    • Nacos服务变更推送流程全解析
    • 深入解析SpringCloud负载均衡器Loadbalancer
    • Nacos源码环境搭建与调试指南
  • Seata

    • 深度剖析Seata源码
  • Docker部署

    • 一文快速掌握docker的理念和基本使用
    • 使用docker编排容器
    • 基于docker-compose部署微服务基本环境
    • 基于docker容器化部署微服务
    • Gateway全局异常处理及请求响应监控
    • Docker图形化界面工具Portainer最佳实践
  • Go基础

    • 一文带你速通Go语言基础语法
    • 一文快速掌握Go语言切片
    • 来聊聊go语言的hashMap
    • 一文速通go语言类型系统
    • 浅谈Go语言中的面向对象
    • go语言是如何实现协程的
    • 聊聊go语言中的GMP模型
    • 极简的go语言channel入门
    • 聊聊go语言基于epoll的网络并发实现
    • 写给Java开发的Go语言协程实践
  • mini-redis实战

    • 来聊聊我用go手写redis这件事
    • mini-redis如何解析处理客户端请求
    • 实现mini-redis字符串操作
    • 硬核复刻redis底层双向链表核心实现
    • 动手复刻redis之go语言下的字典的设计与落地
    • Go 语言下的 Redis 跳表设计与实现
    • Go 语言版 Redis 有序集合指令复刻探索
  • 项目编排

    • Spring脚手架创建简记
    • Spring脚手架集成分页插件
    • Spring脚手架集成校验框架
    • maven父子模块两种搭建方式简记
    • SpringBoot+Vue3前后端快速整合入门
    • 来聊聊Java项目分层规范
  • 场景设计

    • Java实现文件分片上传
    • 基于时间缓存优化浏览器轮询阻塞问题
    • 基于EasyExcel实现高效导出
    • 10亿数据高效插入MySQL最佳方案
    • 从开源框架中学习那些实用的位运算技巧
  • CI/CD

    • 基于NETAPP实现内网穿透
    • 基于Gitee实现Jenkins自动化部署SpringBoot项目
    • Jenkins离线安装部署教程简记
    • 基于Nexus搭建Maven私服基础入门
    • 基于内网的Jenkins整合gitlab综合方案简记
  • 监控方法论

    • SpringBoot集成Prometheus与Grafana监控
    • Java监控度量Micrometer全解析
    • 从 micrometer计量器角度快速上手promQL
    • 硬核安利一个监控告警开源项目Nightingale
  • Spring AI

    • Spring AI Alibaba深度实战:一文掌握智能体开发全流程
    • Spring AI Alibaba实战:JVM监控诊断Arthas Agent的工程化构建与最佳实践
  • 大模型评测

    • M2.7 真能打!我用两个真实场景测了测,结果有点意外
    • Qoder JetBrains插件评测:祖传代码重构与接口优化实战
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 计算机组成原理

  • 操作系统

  • 计算机网络

  • 运维

  • 编码最佳实践

    • 浅谈现代软件工程TDD最佳实践
    • 浅谈TDD模式下并发程序设计与实现
      • 写在文章开头
      • 浅谈并发编程的测试理念
      • TDD模式下有界缓存队列的实现
        • 需求说明
        • 业务功能拆解
        • 设计落地
        • 落地步骤
        • 并发无关性逻辑验证
        • 并发写入一致性验证
        • 并发阻塞验证
        • 安全性测试
        • 重构与优化
      • 小结
      • 参考
    • 面向AI编程新范式Trae后端开发环境搭建与实践
    • 基于提示词工程的Redis签到功能开发实践
    • 基于Vibe Coding的Redis分页查询实现
    • 告别AI无效对话:资深工程师的提示词设计最佳实践
  • 计算机基础
  • 编码最佳实践
sharkchili
2026-01-15
目录

浅谈TDD模式下并发程序设计与实现

# 写在文章开头

测试驱动开发,也就是我们常说的TDD,按照权威资料的说法,TDD实际上不拘泥于任何形式,可以是单元测试也可以是QA等测试迁移的验收标准,总言之这种模式强调的是通过验收标准辅助开发人员理清需求,从而确保每次迭代、重构都能得到明确的反馈,确保功能落地准确性同时,还能够提升研发人员的生产效率。

而本文将从一个基础并发缓存队列,演示一下如何基于TDD模式完成功能开发、迭代、优化,希望对你有所帮助。

你好,我是 SharkChili ,禅与计算机程序设计艺术布道者,希望我的理念对您有所启发。

📝 我的公众号:写代码的SharkChili
在这里,我会分享技术干货、编程思考与开源项目实践。

🚀 我的开源项目:mini-redis
一个用于教学理解的 Redis 精简实现,欢迎 Star & Contribute:
https://github.com/shark-ctrl/mini-redis (opens new window)

👥 欢迎加入读者群
关注公众号,回复 【加群】 即可获取联系方式,期待与你交流技术、共同成长!

# 浅谈并发编程的测试理念

相比于常规的串行程序,并发程序的测试要相对复杂一些,因为它的执行存在更多的未知性,可能需要更长的时间才能将这些随机的未知性暴露。对于并发测试的验收标准,我们一般强调如下两个条件:

  1. 安全性测试:程序不会发生任何错误的行为
  2. 活跃性:某些良好的行为最终会发生

举个简单的例子,假设我们编写一个并发操作安全的队列,按照并发程序的验收标准,安全性测试可以理解为并发操作线程安全,即并发写入10个元素,最终队列中存在的元素就是10个。 同理,活跃性测试则是强调会按照正确的行为发生对应事件,例如阻塞队列容量为10,当写入10个元素后,尝试写入第11个元素的线程会阻塞:

所以对于安全性测试,我们必须学会抓住程序的不变形条件和后验条件,以我们上述说明的并发安全队列为例,则是:

  1. 不变形条件:新建的队列,默认大小为0
  2. 串行10个元素插入长度为10的队列,最终大小也是10,且不会发生阻塞

这里笔者之所以强调串行验收的概念,是因为针对复杂的并发程序而言,编写必要的串行测试单元,有助于发现一些据竞争之外的问题,以降低后续并发测试的排错成本。

接下来我们再来聊聊阻塞式的测试,按照juc一致性测试的说法,所有的故障都必须与明确的测试用例相关联,按照JSR 166专家组的说法:

每个测试必须等到所有创建的线程都结束才能完成

所以,涉及活跃性测试中的阻塞测试这一环,我们必须做到等待所有创建线程完成工作之后,通过某些手段去感知线程阻塞,结合java线程的特性,所有阻塞挂起的线程都可以通过interrupt方法通知其中断响应,所以对于活跃性的后验测试,我们也可以利用这一点做到:

  1. 按照预期要求编写并发程序,等待所有线程运行并达到预期状态(包含阻塞态)
  2. 尝试去打认定为阻塞断线程,感知中断异常确认活跃性问题

# TDD模式下有界缓存队列的实现

# 需求说明

接下来笔者就介绍TDD开发模式下完成一个有界缓存的开发,本案例要求我们实现一个并发安全的有界缓存队列的实现要求为:

  1. 可灵活指定指定缓存队列容量
  2. 没达到上限时非阻塞追加写入元素
  3. 成功写入一个元素,对应队列长度+1
  4. 取出元素时队列长度扣减,按照先进先出原则
  5. 缓存队列达到上界后线程阻塞,直到队列有空闲的空间容纳待写入的元素

# 业务功能拆解

需求比较简单,所以针对业务功能也没有很复杂的拆解,本质上就是需要生成一个具备如下行为的缓存队列BoundedQueue:

  1. 可以指定队列长度
  2. 自持查询当前队列长度以及队列是否已满
  3. 支持添加元素,以追加的方式
  4. 支持移除元素,从队首移除

限定条件:

  1. 支持并发操作,即多个线程并发存入,最终得到元素大小就是成功存入队里的元素数
  2. 队列无法容纳元素时,生产者线程会阻塞

基于上述业务需求的理解和功能拆解,我们大体得出需要实现如下几个方法:

  1. 一个可以指定队列长度的构造方法
  2. 一个将元素存入缓存队列的put方法,在队列未达到上限时会阻塞当前线程
  3. 一个将元素取出队列的take方法,当队列没有元素的时候会阻塞当前线程

# 设计落地

结合对于需求的梳理,本次缓存队列的第一版本的实现,比这打算用经典的生产者消费者模式的condition变量来实现,大体来说实现的是通过非空和非满和条件控制写入元素的生产者和消费元素的take调用者正确阻塞和唤醒操作,整体实现思路为:

  1. 声明缓存队列BoundedQueue,内部聚合一个链表存储元素
  2. 声明生产或者消费队列时要用到的互斥锁lock,确保并发操作互斥
  3. 基于lock创建notFull队列,当队列已满时阻塞生产者的等待条件队列notFull
  4. 基于lock创建notEmpty队列,当队列已空时阻塞生产者的等待队列非空的条件队列notEmpty
  5. 生产元素时,首先获取putLock,成功获取所后,判断队列是否已满,若已满则调用notFull的wait方法等待非满时被唤醒,若非空则写入返回
  6. 消费元素时,首先获取takeLock,确保消费元素操作互斥,若为空则调用notEmpty的wait将其阻塞,等待有元素时被唤醒

对应的比这也给出基于这个生产者消费者模型,得出的经典交互流程图:

明确开发思路之后,我们就可以按照并发程序的安全性和活跃性验收标准完成驱动测试用例的设计,辅助理解并梳理并发程序的要求和验收标准,确保开发方向正确且顺序执行,不会被传统TDD所谓红绿灯步骤打断心流:

  1. 安全性测试:初始化队列队列size为0
  2. 安全性测试:确保空队列添加元素后,返回的size为1
  3. 安全性测试:指定队列为10的情况下,10个并发线程都那正确添加成功,且最终size为10
  4. 活跃性测试:指定队列为10的情况下,11个线程操作,有一个线程会阻塞

这里笔者专门抽出一个版块,着重强调一个最重要的安全测试,考虑到读者跑程序机器不确定性即在硬件条件较差的机器测试并发程序,很可能会因为创建大量线程导致并行的程序变为串行执行,所以在安全性测试这一块,笔者会考虑如下步骤完成的一个准确的并发正确性测试,对应落地步骤为:

  1. 创建CyclicBarrier,当所有生产者、消费者线程就绪后并发生产者消费元素,确保所有线程就绪后,并行交替工作
  2. 指定读写元素为整型,且为随机数,这样做的目的是避免编译器预先猜到校验和而去提前优化,导致安全性测试不准确,通过原子类记录生产者消费者处理元素的累加和比对一致进行安全性校验

# 落地步骤

因为有上述的设计方案和演示测试用例,笔者很清晰的得出下面这个核心代码片段,整体编码思路为:

  1. 声明可重入锁确保并发互斥
  2. 基于锁构建生产者和消费者的等待条件队列,即生产者监听notFull等待队列非满写入元素,消费者监听notEmpty等待非空消费元素
  3. 写入元素函数put,上锁后判断队列是否非满,若满则阻塞,反之写入,消费者同理

对应代码示例如下,读者可结合注释理解一下实现细节

public class BoundedQueue<E> {


    private final ReentrantLock lock = new ReentrantLock();

    /**
     * 生产者写入元素后,会通知notEmpty等待队列的消费者执行消费
     */
    private final Condition notEmpty = lock.newCondition();

    /**
     * 消费者获取元素后,会通知notFull等待队列的生产者执行写入
     */
    private final Condition notFull = lock.newCondition();

    private LinkedList<E> items;

    private int capacity;

    private int size = 0;

    //初始化容量和队列(底层用链表)
    public BoundedQueue(int capacity) {
        this.capacity = capacity;
        items = new LinkedList<>();

    }

    /**
     * 上锁成功后根据是否到达上限决定写入还是阻塞
     * @param e
     * @throws InterruptedException
     */
    public void put(E e) throws InterruptedException {
        try {
            lock.lockInterruptibly();
            //避免唤醒后又满 依然执行写入的错误情况
            while (items.size() == capacity) {
                notFull.await();
            }
            //添加元素
            items.add(e);

            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }

    public E take() throws InterruptedException {
        try {
            lock.lockInterruptibly();
            //等待非空唤醒消费
            while (items.size() == 0) {
                notEmpty.await();
            }
            //获取元素
            E e = items.remove();
            //消费元素,通知生产者继续写入
            notFull.signal();
            return e;
        } finally {
            lock.unlock();
        }
    }

    public int size() {
        return items.size();
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71

# 并发无关性逻辑验证

同理顺序给出上述几个驱动的测试设计驱动阶段的测试用例落地,第一个则是基本添加功能的验收比较容易:

 //确保空队列添加元素后,返回的size为1
    @Test
    void testAddToEmptyQueue() throws InterruptedException {
        BoundedQueue queue = new BoundedQueue(1);
        queue.put(1);
        assertEquals(1, queue.size());
    }
1
2
3
4
5
6
7

# 并发写入一致性验证

第二个则是并发功能的验收,这一点可以参考并发编程的说法,采用后验条件进行验收,由需求可知正确的结构是长度为10的队列10个并发添加是可以正确添加且不阻塞的,所以我们只需通过最终的size即后验条件验收即可:

//指定队列为10的情况下,10个并发线程都那正确添加成功
    @Test
    void testAddToFullQueue() throws InterruptedException {
        BoundedQueue queue = new BoundedQueue(10);
        List<CompletableFuture<Void>> completableFutureList = IntStream.range(0, 10)
                .mapToObj(i -> CompletableFuture.runAsync(() -> {
                    try {
                        queue.put(i);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }))
                .collect(Collectors.toList());

        completableFutureList.stream()
                .map(CompletableFuture::join) //用join阻塞获取结果
                .collect(Collectors.toList());//组成列表

        assertEquals(10, queue.size());


    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 并发阻塞验证

最后一个涉及阻塞操作,如未能正确处理可能导致后验逻辑无法执行,所以我们必须做到让阻塞的线程走到一个目标逻辑方法上。 结合并发编程基础可知,阻塞操作是可以打断的,所以我们的测试可以在等待线程阻塞后将其打断,通过判断线程的存活以及逻辑是否走到阻塞之后的代码段感知阻塞态。

对应代码如下,笔者给定时间等待线程阻塞,然后尝试将其打断确保可以顺利执行后验逻辑,如果触发中断异常则将isBlocking状态设置为true,后验逻辑就可以根据这个标识进行判断:

 //指定队列为1且被使用的情况下,另外一个线程会阻塞
    @Test
    void testAddToFullQueueWithBlocking() throws InterruptedException {
        //设置队列长度为1并写入
        AtomicBoolean isBlocking = new AtomicBoolean(false);
        BoundedQueue queue = new BoundedQueue(1);
        queue.put(1);
        //创建线程再次写入
        Thread thread = new Thread(() -> {
            try {
                queue.put(RandomUtil.randomInt(1000));
                //若走到这里说明线程没有阻塞
                isBlocking.set(false);
            } catch (InterruptedException success) {
                //若线程走到这里则说明成功被打断
                isBlocking.set(true);
            }

        });

        thread.start();
        //让当前线程等待指定线程最多1000毫秒
        thread.join(1000);
        //尝试打断线程
        ThreadUtil.sleep(1000);
        thread.interrupt();
        ThreadUtil.sleep(1000);

        //被打断的线程会死亡,isAlive会变为false
        assertFalse(thread.isAlive());
        //判断是否被阻塞,若打断则原子状态会被设置为true
        assertTrue(isBlocking.get());

    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

# 安全性测试

最后就是并发数据竞争写入的校验了,和笔者上述设计一致,通过CyclicBarrier确保线程并发无序执行,生产者消费者执行随机数的生产和消费并通过原子类求和,在后验逻辑中验证双方求和是否一致以做到安全性测试校验:


    @Test
    void testConcurrentSafety() throws InterruptedException, BrokenBarrierException {
        int size = Runtime.getRuntime().availableProcessors();
        //创建2N+1个循环栅栏
        CyclicBarrier barrier = new CyclicBarrier((size << 1) + 1);
        ExecutorService threadPool = Executors.newFixedThreadPool(2 * size + 1);

        //计算写入和消费元素和
        AtomicInteger putSum = new AtomicInteger();
        AtomicInteger takeSum = new AtomicInteger();
        //初始化阻塞队列
        BoundedQueue<Integer> queue = new BoundedQueue(100);
        
        //执行N次循环,对应会消费2N个循环栅栏
        for (int i = 0; i < size; i++) {
            threadPool.execute(() -> {
                try {
                    //消费启动栅栏,就绪等待
                    barrier.await();
                    int num = RandomUtil.randomInt(1000);
                    queue.put(num);
                    putSum.addAndGet(num);
                    barrier.await();
                } catch (Exception e) {
                    e.printStackTrace();
                }

            });

            threadPool.execute(() -> {
                try {
                    //消费启动栅栏,就绪等待
                    barrier.await();
                    takeSum.addAndGet(queue.take());
                    barrier.await();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
        //消费第2N+1个循环栅栏,启动并发
        barrier.await();
        //2次消费第2N+1个循环栅栏,结束并发
        barrier.await();
        //校验和
        assertEquals(putSum.get(), takeSum.get());


    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

# 重构与优化

有了上述基础之后,我们后续的重构和优化就变得非常容易了,例如:笔者考虑到当前服务器内存敏感,考虑到阻塞队列容量固定,打算将有界缓存队列底层链表换为局部性友好的数组,此时对应迭代回归我们都可以用到上述的单元测试很快完成迭代后的验收:

 //初始化容量和队列(底层用数组提升执行性能)
    public BoundedQueue(int capacity) {
        this.capacity = capacity;
        items = new ArrayList<>(capacity);

    }

1
2
3
4
5
6
7

# 小结

本文详细介绍TDD与传统并发编程程序的设计与实现思路,并结合一些传统的测试技术让java并发起程序安全预期的随机并发执行,避免动态编译等语言特性所带来的误导,最后等待所有创建线程结束后针对不变形条件或者后验条件(例如本文队列中的size或者并发计算时的随机数求和)执行TDD自动化断言,完成逻辑验收。

你好,我是 SharkChili ,禅与计算机程序设计艺术布道者,希望我的理念对您有所启发。

📝 我的公众号:写代码的SharkChili
在这里,我会分享技术干货、编程思考与开源项目实践。

🚀 我的开源项目:mini-redis
一个用于教学理解的 Redis 精简实现,欢迎 Star & Contribute:
https://github.com/shark-ctrl/mini-redis (opens new window)

👥 欢迎加入读者群
关注公众号,回复 【加群】 即可获取联系方式,期待与你交流技术、共同成长!

# 参考

《java并发编程实战》

编辑 (opens new window)
上次更新: 2026/01/15, 13:48:50
浅谈现代软件工程TDD最佳实践
面向AI编程新范式Trae后端开发环境搭建与实践

← 浅谈现代软件工程TDD最佳实践 面向AI编程新范式Trae后端开发环境搭建与实践→

最近更新
01
基于EasyExcel实现高效导出
03-25
02
从开源框架中学习那些实用的位运算技巧
03-25
03
浅谈分布式架构设计思想和常见优化手段
03-25
更多文章>
Theme by Vdoing | Copyright © 2025-2026 Evan Xu | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×
×