news 2026/9/30 7:37:57

Java Balking 模式实战:用洗衣机案例掌握并发状态守卫编程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java Balking 模式实战:用洗衣机案例掌握并发状态守卫编程
  • 示例工程
  • 教程

【免费下载链接】java-design-patterns

Design patterns implemented in Java

项目地址:https://gitcode.com/GitHub_Trending/ja/java-design-patterns
点击查看免费下载

Balking(犹豫/却步)模式是 Java 并发领域的一种状态守卫设计模式:当对象处于不完整或不合适的状态时,直接拒绝执行请求的代码逻辑,从而避免错误操作与并发冲突。本文以本仓库 balking 模块中的洗衣机(WashingMachine)案例为主线,完整讲解 Balking 模式的设计意图、线程安全实现、运行效果、适用场景与权衡取舍,并结合仓库源码、测试用例与运行方式给出可复制、可验证的实战方案。

Balking 模式的设计意图

Balking 模式的核心意图是:只有当对象处于特定状态时,才允许其执行某个动作;否则方法直接返回、不做任何事。

在 Java 多线程应用中,这一模式是管理状态与并发安全的关键手段。它属于 Concurrency(并发)类模式,兼具以下特性:

  • Decoupling(解耦):把「状态是否合法」的判断收敛到对象内部,调用方无需关心状态细节;
  • Fault tolerance(容错):在状态不合适时安静返回,不抛异常、不产生副作用,避免级联故障;
  • Synchronization(同步):配合synchronized等机制保证状态检查与状态切换的原子性。

Wikipedia 对该模式的经典描述是:例如某个对象读取 ZIP 文件,当 ZIP 文件尚未打开时,调用方请求 get 方法,对象就会"balk"(拒绝)这个请求——这正是 Balking 模式的行为语义。

现实世界类比:自助洗衣机的安全门锁

想象一台投币洗衣机的场景:只有当舱门正确关闭并锁定时,机器才会开始洗衣。如果用户在门还开着时按下启动键,机器会"balk"——什么都不做。这保证了洗涤过程只在安全状态下开始,避免水溢出或损坏设备。软件中的 Balking 模式同理:只有对象处于合适状态时才执行操作,从而防止错误动作、维持系统稳定。

这个类比精准概括了 Balking 模式的两个关键点:前置状态检查与静默拒绝。

程序化示例:洗衣机如何"balk"

本仓库 balking 模块用一个多线程洗衣机程序演示了 Balking 模式:洗衣机有一个启动按钮,机器空闲(ENABLED)时按钮正常工作,机器已在洗涤(WASHING)时按钮按下后什么都不做。

状态枚举与核心类结构

WashingMachine对象仅有两种状态,定义在 WashingMachineState.java:

public enum WashingMachineState { ENABLED, WASHING }

从 balking.urm.puml 的 UML 结构可以看清整个模块的类关系:App(程序入口)、WashingMachine(业务主体)、WashingMachineState(状态枚举)、DelayProvider(延迟执行抽象接口),其中WashingMachine聚合持有状态枚举与延迟提供者。

WashingMachine 核心实现

以下是 WashingMachine.java 的完整实现,也是 Balking 模式的关键代码:

@Slf4j public class WashingMachine { private final DelayProvider delayProvider; @Getter private WashingMachineState washingMachineState; /** Creates a new instance of WashingMachine. */ public WashingMachine() { this( (interval, timeUnit, task) -> { try { Thread.sleep(timeUnit.toMillis(interval)); } catch (InterruptedException ie) { LOGGER.error("", ie); Thread.currentThread().interrupt(); } task.run(); }); } /** * Creates a new instance of WashingMachine using provided delayProvider. * This constructor is used only for unit testing purposes. */ public WashingMachine(DelayProvider delayProvider) { this.delayProvider = delayProvider; this.washingMachineState = WashingMachineState.ENABLED; } /** Method responsible for washing if the object is in appropriate state. */ public void wash() { synchronized (this) { var machineState = getWashingMachineState(); LOGGER.info("{}: Actual machine state: {}", Thread.currentThread().getName(), machineState); if (this.washingMachineState == WashingMachineState.WASHING) { LOGGER.error("Cannot wash if the machine has been already washing!"); return; } this.washingMachineState = WashingMachineState.WASHING; } LOGGER.info("{}: Doing the washing", Thread.currentThread().getName()); this.delayProvider.executeAfterDelay(50, TimeUnit.MILLISECONDS, this::endOfWashing); } /** Method is responsible for ending the washing by changing machine state. */ public synchronized void endOfWashing() { washingMachineState = WashingMachineState.ENABLED; LOGGER.info("{}: Washing completed.", Thread.currentThread().getId()); } }

代码中蕴含三个 Balking 模式的实现要点:

  1. 同步块内的检查-执行(check-then-act):wash()在synchronized (this)块内先读取状态、再决定是否执行。将「状态检查」与「状态切换」放在同一把锁内,保证多线程下检查与修改的原子性,这是 Balking 模式线程安全的基础;
  2. 静默拒绝:当状态已是WASHING时,方法打印错误日志并return,不执行任何洗涤逻辑,也不抛异常——这正是"balk"的语义;
  3. 状态即契约:endOfWashing()同样以synchronized修饰,将状态回置为ENABLED,与wash()形成完整的状态闭环。

DelayProvider:延迟执行抽象

DelayProvider.java 是一个极简接口,作用是模拟洗涤耗时并把「时间控制」从业务逻辑中剥离:

public interface DelayProvider { void executeAfterDelay(long interval, TimeUnit timeUnit, Runnable task); }

该抽象有两个关键价值:默认实现(WashingMachine无参构造中的 Lambda)通过Thread.sleep模拟 50 毫秒的洗涤耗时;而测试代码则注入FakeDelayProvider,将延迟完全"冻结",从而可以确定性、无等待地验证状态流转(详见下文测试章节)。

应用入口与运行输出

App.java 演示了真实的多线程竞争场景——用固定大小为 3 的线程池同时提交 3 次wash()调用:

public static void main(String... args) { final var washingMachine = new WashingMachine(); var executorService = Executors.newFixedThreadPool(3); for (int i = 0; i < 3; i++) { executorService.execute(washingMachine::wash); } executorService.shutdown(); try { if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) { executorService.shutdownNow(); } } catch (InterruptedException ie) { LOGGER.error("ERROR: Waiting on executor service shutdown!"); Thread.currentThread().interrupt(); } }

程序运行的控制台输出(来自原文档,pool-1-thread-N为线程池线程名):

14:02:52.268 [pool-1-thread-2] INFO com.iluwatar.balking.WashingMachine - pool-1-thread-2: Actual machine state: ENABLED 14:02:52.272 [pool-1-thread-2] INFO com.iluwatar.balking.WashingMachine - pool-1-thread-2: Doing the washing 14:02:52.272 [pool-1-thread-3] INFO com.iluwatar.balking.WashingMachine - pool-1-thread-3: Actual machine state: WASHING 14:02:52.273 [pool-1-thread-3] ERROR com.iluwatar.balking.WashingMachine - Cannot wash if the machine has been already washing! 14:02:52.273 [pool-1-thread-1] INFO com.iluwatar.balking.WashingMachine - pool-1-thread-1: Actual machine state: WASHING 14:02:52.273 [pool-1-thread-1] ERROR com.iluwatar.balking.WashingMachine - Cannot wash if the machine has been already washing! 14:02:52.324 [pool-1-thread-1] INFO com.iluwatar.balking.WashingMachine - 14: Washing completed.

输出清晰地呈现了竞争结果:线程 2 抢先把状态从ENABLED置为WASHING并开始洗涤;随后线程 3、线程 1 到达时发现状态已是WASHING,随即 balk 拒绝;50 毫秒后洗涤结束,状态回到ENABLED。3 个并发请求只有 1 个真正执行,这正是 Balking 模式的价值所在。

源码佐证:测试用例如何验证 Balking 行为

仓库中的 WashingMachineTest.java 用单元测试精确验证了 Balking 语义:

@Test void wash() { var washingMachine = new WashingMachine(fakeDelayProvider); washingMachine.wash(); washingMachine.wash(); var machineStateGlobal = washingMachine.getWashingMachineState(); fakeDelayProvider.task.run(); // washing machine remains in washing state assertEquals(WashingMachineState.WASHING, machineStateGlobal); // washing machine goes back to enabled state assertEquals(WashingMachineState.ENABLED, washingMachine.getWashingMachineState()); }

测试要点:

  • 连续两次wash():第一次成功(状态置为WASHING),第二次因状态不合法而 balk,印证了"第二次调用不做任何事";
  • FakeDelayProvider:测试用 内部类 FakeDelayProvider 捕获传入的Runnable,随后手动run()触发洗涤结束,从而无需真实等待即可验证状态由WASHING回落到ENABLED;
  • endOfWashing() 测试:单独调用后断言状态回到ENABLED,验证状态回置逻辑;
  • AppTest.java 则通过assertDoesNotThrow(App::main)保证整个并发程序可无异常执行。

如何运行本示例

balking是 Maven 多模块项目 java-design-patterns 的一个子模块(见 balking/pom.xml,依赖 slf4j-api、logback-classic 与 JUnit 5),运行方式如下:

# 在仓库根目录构建并运行 App(mainClass 已配置为 com.iluwatar.balking.App) ./mvnw -pl balking compile exec:java # 或直接运行测试验证 Balking 行为 ./mvnw -pl balking test

提示:App不接收任何命令行参数(见 App.java 的main(String... args)注释),多线程竞争行为由代码内部固定线程池产生;输出日志格式与线程调度顺序可能因机器而异,但"仅一个线程成功洗涤、其余 balk"的最终结论不变。

何时使用 Balking 模式

适合采用 Balking 模式的情况:

  • 仅在对象处于特定状态时执行动作:动作的合法性完全由对象当前状态决定;
  • 对象的"不合法状态"是临时性的,但持续时间不确定:例如资源初始化中、连接建立中、任务执行中等,外部因素或并发操作会让状态随时间变化;
  • 多线程应用中,动作需要满足特定条件才能继续,而这些条件会因外部因素或并发操作而动态变化。

典型应用场景(来自原文档):

  • 资源池(Resource pooling):仅在资源处于可分配的有效状态时才进行分配;
  • 线程管理(Thread management):仅当任务可用、资源锁满足等特定条件成立时,线程才继续执行任务。

收益与权衡

收益

  • 减少不必要的锁竞争:在动作无法推进时直接返回,避免无意义地长时间占用锁,提升并发应用的吞吐与性能;
  • 职责分离更清晰:状态管理与行为逻辑被明确分离,代码更干净、可读性更强;
  • 调用方更简洁:把「仅在某些条件下执行」的复杂判断收敛到对象内部,调用方代码无需堆砌大量状态检查。

权衡

  • 隐式条件增加调试难度:动作何时执行、何时被忽略的条件被封装在对象内部,可能让系统更难调试和理解;
  • 可能错失动作机会:如果状态变化没有被恰当监控,或 balk 条件设置得过于严格,可能导致应该执行的动作被跳过。

与相关 Java 设计模式的关联

Balking 模式常与以下模式配合或对照使用:

  • Double-Checked Locking:确保初始化只在必要时发生并避免不必要的加锁。与 Balking 一样,都依据对象状态来条件性执行逻辑;
  • Guarded Suspension:同样保证动作只在对象处于特定状态时执行,但 Guarded Suspension 通常会让线程等待状态变为有效,而 Balking 是直接放弃;
  • State:State 模式可用来管理对象的全部状态与状态转移,与 Balking 结合使用可以更规范地组织状态机逻辑。

理解三者的差异非常关键:Balking 是"状态不合适就立即返回",Guarded Suspension 是"状态不合适就等待",二者分别对应"拒绝"与"阻塞"两种策略,可根据业务对时延与成功率的诉求取舍。

小结

通过本仓库 balking 模块的洗衣机案例,我们完整掌握了 Balking 模式:以synchronized保证状态检查-执行原子性、状态不合法时静默返回、再配合DelayProvider解耦耗时逻辑,最后用线程池制造真实竞争、用单元测试锁定行为语义。在多线程 Java 应用中,当你想让某个操作"只在对象处于合适状态时发生",Balking 就是最简单直接的并发守卫方案。

  • 示例工程
  • 教程

【免费下载链接】java-design-patterns

Design patterns implemented in Java

项目地址:https://gitcode.com/GitHub_Trending/ja/java-design-patterns
点击查看免费下载

相关推荐

上一篇:TypeScript 类型合并(Merging)与扩展(Extension)实战指南:来自 The Concise TypeScript Book 的权威解读
下一篇:免费解锁WeMod高级功能:Wand-Enhancer完整使用指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/30 7:37:44

快捷支付原理与对接实践:从代扣协议到接口避坑全解析

1. 快捷支付到底是什么快捷支付这个词&#xff0c;天天在微信、支付宝、银联云闪付里看到&#xff0c;但真要让人解释清楚它和普通支付有什么区别&#xff0c;不少人还真说不利索。我最早接触到这个概念的时侯是在银行后台做清算系统对接&#xff0c;那时候才发现快捷支付并不是…

作者头像 李华
网站建设 2026/9/30 7:36:06

维特智能WTGPS-02H在铁塔气象雷达定位定向中的应用

导语西南地区某气象科技企业在铁塔上安装天气雷达时&#xff0c;因铁塔金属结构对磁力计产生强磁干扰&#xff0c;导致传统定向方式无法提供准确航向角。该企业选用维特智能WTGPS-02H双天线定向模组&#xff0c;通过双天线GNSS基线解算航向角&#xff0c;从根本上规避了磁干扰问…

作者头像 李华
网站建设 2026/9/30 7:35:59

异步调用大模型接口时Broken pipe的根因排查与修复

1. 事故现场&#xff1a;一场诡异的线上告警前几天晚上十一点多&#xff0c;我正打算关电脑&#xff0c;群里突然炸了。运营反馈说某个后台功能里的“AI总结”按钮转圈转了半天&#xff0c;最后弹了个“服务异常&#xff0c;请稍后重试”。我心里咯噔一下&#xff1a;这功能上线…

作者头像 李华
网站建设 2026/9/30 7:35:33

HTTP协议实战避坑:从报文结构、缓存机制到抓包排查的工程指南

简介&#xff1a;这是一份面向有一定网络基础的开发人员与技术爱好者的HTTP协议系统学习资料&#xff0c;聚焦从报文结构、请求方法、URI与状态码等基础概念&#xff0c;到无状态性、明文传输、队头阻塞、跨域、缓存、代理等实际痛点的深入剖析&#xff0c;并延伸至TLS握手&…

作者头像 李华
网站建设 2026/9/30 7:35:33

混合精度训练崩溃之谜:手写梯度缩放,彻底根治NaN

训练跑着跑着 loss 变成 NaN&#xff0c;这大概是每个搞深度学习的人都经历过的噩梦。早期我遇到这种情况&#xff0c;第一反应是调小学习率、清理数据、换初始化&#xff0c;结果发现治标不治本。真正让我彻底理解问题根源的&#xff0c;是后来深入研究自动混合精度&#xff0…

作者头像 李华
网站建设 2026/9/30 7:35:18

HR 避坑:2026 人才测评工具 5 大常见使用误区拆解

过去一年里&#xff0c;人才测评工具的市场规模持续走高&#xff0c;全球员工绩效评估平台预计在2026年达到54亿美元。但工具多了&#xff0c;踩坑的人也多了。不少HR把测评买回来才发现&#xff1a;候选人不配合、报告看不懂、数据躺在后台没人用。问题不在于工具本身&#xf…

作者头像 李华