无状态的轻量级状态机Cola理解

Posted by SFHJavaer on 2025-06-01
Estimated Reading Time 6 Minutes
Words 1.5k In Total
Viewed Times

Spring StateMachine采用重量级锁保证线程安全,有状态

这里的有无状态是指:

有状态:实例内部保存当前状态(<font style="color:rgb(0, 0, 0);">current state</font>)和上下文信息。状态转换时,实例内部状态会自动更新。如果不使用线程池,那么每次都会创建new一个状态机对象,然后默认为起始状态

无状态:状态都是外部明确传入的,我们只需要他的状态流转,而状态机不保存当前事物的状态,表现了它可以被 单例化 ,表明了状态机是可以重复利用的

无状态优缺点:

  • 优点
    资源利用率高(单例即可),适合分布式系统。
  • 缺点
    状态需外部管理,代码侵入性较强。

核心概念

:::info

  • State:状态
  • Event:事件,状态由事件触发,引起变化
  • Transition:流转,表示从一个状态到另一个状态
  • External Transition:外部流转,两个不同状态之间的流转
  • Internal Transition:内部流转,同一个状态之间的流转
  • Condition:条件,表示是否允许到达某个状态
  • Action:动作,到达某个状态之后,可以做什么
  • StateMachine:状态机

:::

顶层接口

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
public interface StateMachine<S, E, C> extends Visitable{
/**
* Verify if an event {@code E} can be fired from current state {@code S}
* @param sourceStateId
* @param event
* @return
*/
boolean verify(S sourceStateId,E event);

/**
* Send an event {@code E} to the state machine.
*
* @param sourceState the source state
* @param event the event to send
* @param ctx the user defined context
* @return the target state
*/
S fireEvent(S sourceState, E event, C ctx);

/**
* MachineId is the identifier for a State Machine
* @return
*/
String getMachineId();

/**
* Use visitor pattern to display the structure of the state machine
*/
void showStateMachine();

String generatePlantUML();
}

接口声明了3个泛型类,**<font style="color:rgb(192, 52, 29);background-color:rgb(251, 229, 225);">S</font>**表示状态类型,**<font style="color:rgb(192, 52, 29);background-color:rgb(251, 229, 225);">E</font>**表示事件类型,**<font style="color:rgb(192, 52, 29);background-color:rgb(251, 229, 225);">C</font>**表示上下文类型

:::info
S、E:核心流转条件

C: 状态机的上下文信息,使用上下文信息,我们可以在状态转换过程中传递一些额外的数据和参数,方便进行状态机的状态转换和业务逻辑的处理。

:::

状态变更方法
  • fireEvent(S sourceState, E event, C ctx):将指定事件发送给状态机,触发状态转换,并返回目标状态。该方法是状态机进行状态转换的核心逻辑,可以根据当前状态和事件来计算出目标状态,并执行相应的动作或逻辑。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
触发事件 (fireEvent)

[检查所有可能的状态流转]

匹配 from 状态 + 事件类型

执行 when 条件判断
|→ 条件不满足 → 终止处理,状态不变

条件满足

执行 perform 动作

更新状态到 to 状态

  • 条件优先原则:状态机会先进行 <font style="color:rgb(0, 0, 0);">when()</font> 条件判断,只有条件满足时才会执行后续操作
  • 原子性保证:整个过程是原子性的,条件判断 → 执行业务动作 → 状态变更 是一个完整的事务单元
  • 状态变更时机:状态变为 <font style="color:rgb(0, 0, 0);">to</font> 的状态仅发生在所有条件通过且业务动作执行完成之后

PS:内部状态流转

虽然不管when是否成立都是保持当前的状态,但是为了fireEvent的时候确定要执行的Transition,所以内部流转也是要写当前状态的,不写不知道是哪个状态的内部流转

参考文档:

https://www.cnblogs.com/johnnyzen/p/18406389

工作流比较

  • 工作流(WorkFlow),大体是指业务过程(整体或者部分)在计算机应用环境下的自动化,是对工作流程及其各操作步骤之间业务规则的描述。在计算机系统中,工作流属于计算机支持的协同工作(CSCW)的一部分。
  • 状态机工作流(WorkFlow)的一种类型,包括顺序工作流(Sequential)和状态机工作流(State Machine)。
特性 状态机(State Machine) 工作流(WorkFlow)
关注点 单个任务 状态流转
循环 可以简单的实现循环 无循环
实现难度 比较麻烦,需要记录任务当前状态 实现简单
表达方式 表达更灵活 串行表达,不是很灵活
运行效率 低,可并行

状态机实战

最基础的构建一个流转:

1
2
3
4
5
6
7
8
9
10
StateMachineBuilder<OrderStatusEnum, OrderEventEnum, OrderContext> builder
= StateMachineBuilderFactory.create();


builder.externalTransition()
.from(States.STATE1)
.to(States.STATE2)
.on(Events.EVENT1)
.when(checkCondition())
.perform(doAction());

我们将其修改为注解类型实现,更优雅一些,创建组件注解

然后我们在创建这个状态机实例的时候,就装载好所有的流转

组装方法例子

最后的action部分用来组装perform的执行逻辑

写一个factory,将所有的状态机实例放到map中

前面的装载是放到了构造中,我们测试时new来使用。

实际中,我们可以直接写在BeanPostProcesor中,创建bean的时候如果扫描到相应注解,做对应封装,封装方法是完全一致的

实际使用时,创建流转直接返回Action即可

最后从业务组件,调用到某个处理引擎,然后处理引擎实际调用fireevent来触发流转和业务底层处理

fireEvent双要素

:::info
此处和泛化调用、反射笔记中都有这种要素,就是根据什么,能确定我的invoke方法、泛化方法、还有流转

:::

双要素:S+E 当前状态+触发事件 = 确定唯一的流转


为什么我们可以直接把创建的实例直接用class作为map的key进行存储呢?

原因是我们只需要拿到一个状态机实例,然后

本质从StateMachineFactory.get(machineId) 获取状态机,不允许重复构建,重复构建也会报错。

参考:https://www.cnblogs.com/Zero-Jo/p/14622937.html


如果您喜欢此博客或发现它对您有用,则欢迎对此发表评论。 也欢迎您共享此博客,以便更多人可以参与。 如果博客中使用的图像侵犯了您的版权,请与作者联系以将其删除。 谢谢 !