“状态机四要素:现态,条件,动作,次态

状态机,通常被用来管理一个实体的生命周期。通过它,可以使复杂的状态转化问题模式化,达到代码高内聚的效果。事实上,状态机也确实是一种设计模式。

状态机有四个要素:

  • 现态:实体的当前状态
  • 次态:实体的下个状态
  • 条件:引发实体状态转移的条件
  • 动作:条件发生时,伴随的动作

示意图如下:

android 状态机 demo_java

01

目标代码

状态机的代码路径如下:

./hadoop-release-2.7.0/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/state

这个模块的代码是在YARN项目下,所以设计目的也是只针对YARN。

该路径,包含下列代码:

state $ tree.|-- Graph.java|-- InvalidStateTransitionException.java|-- MultipleArcTransition.java|-- package-info.java|-- SingleArcTransition.java|-- StateMachine.java|-- StateMachineFactory.java|-- VisualizeStateMachine.java

先对其中的工具代码做简要介绍:

Graph/VisualizeStateMachine: 这两个文件的目的是为了将状态机中的复杂关系可视化。

InvalidStateTransitionException: 该模块独有的异常。

接下来看剩下的这些代码的类图关系。

02

类图关系

先看SingleArcTransition和MultipleArcTransition。

android 状态机 demo_源码 状态机_02

这两个都是接口类,其实并没有什么类图关系。放在一起主要是想指出两点。

第一,为什么要从概念上区分两者,差别在哪里。对于SingleArc来说,它的现态和次态是单一确定的;但是对于MultipleArc来说,它的次态则可以有多个。transition的方法的返回值其实也暗示了这点。前者返回void,因为状态关系明确;后者返回STATE,需要指明究竟是多个次态中的哪一个。真正能够说明两者区别的代码在StateMachineFactory中,它也是本文的唯一关键类,我们放在关键类分析中细讲。

第二,前文提到,状态机的四要素是现态,次态,条件,动作。而实际上,我们transition方法,其实只包含了两项,即条件(对应event)和动作(对应方法本身),第一个参数operand是状态机关联的实体。那么为什么没有包含现态和次态两项呢?一个揣测是,接口设计者的本意是通过operand来访问两个状态。

但是,StateMachineFactory的实现者似乎不喜欢这个不透明的接口,所以之后分析该类代码时,我们会发现其内部,又定义了一个Transition接口,及其实现   SingleInternalArc和MultipleInternalArc,两者分别对原始接口进行了封装。

再来看StateMachine,它也是一个接口类,类图如下:

android 状态机 demo_android 状态机 demo_03

最后是StateMachineFactory,是一个实现类。其内部又定义了一些接口及实现,类图如下:

android 状态机 demo_android 状态机 demo_04

03

关键类分析

状态机模块只有一个关键类,即StateMachineFactory。从类图可以看出它的复杂性。现在一点点剖析。

首先,以Factory结尾的类都是工厂类,遵循工厂模式,其目的就是将构建目标的过程(StateMachineFactory)和目标(StateMachine)本身解耦。

这个工厂的关键目标有两个:

  • 添加转移过程(addTransition)
  • 基于已经添加的转移过程,构建状态机(make)

那么先看addTransition,这个方法有很多签名:

public StateMachineFactory addTransition(STATE preState, STATE postState, EVENTTYPE eventType); # 单个事件类型,不包含动作public StateMachineFactory addTransition(STATE preState, STATE postState, Set eventTypes); 多个事件类型,不包含动作public StateMachineFactory addTransition(STATE preState, STATE postState, Set eventTypes, SingleArcTransition hook); 多个事件类型,包含SingleArcpublic StateMachineFactoryaddTransition(STATE preState, STATE postState,EVENTTYPE eventType,SingleArcTransition hook); 单个事件类型,包含SingleArcpublic StateMachineFactoryaddTransition(STATE preState, Set postStates,EVENTTYPE eventType,MultipleArcTransition hook); 单个事件类型,包含MultipleArc,多个次态

真正起作用的是最后两个,而他们本质上是调用了private标识的构造函数:

private StateMachineFactory      (StateMachineFactory that,       ApplicableTransition t) {    this.defaultInitialState = that.defaultInitialState;    this.transitionsListNode         = new TransitionsListNode(t, that.transitionsListNode); # 追加transitionsListNode链表    this.optimized = false;    this.stateMachineTable = null; # 清空状态表  }

再看make:

public StateMachinemake(OPERAND operand) {    return new InternalStateMachine(operand, defaultInitialState);  }# 构造函数  InternalStateMachine(OPERAND operand, STATE initialState) {      this.operand = operand;      this.currentState = initialState;      if (!optimized) {        maybeMakeStateMachineTable(); # 构建状态表      }    }

构建的核心逻辑在MakeStateMachineTable中:

private void makeStateMachineTable() {    Stack> stack =      new Stack>();    MapMap      prototype = new HashMapMap    prototype.put(defaultInitialState, null);    // I use EnumMap here because it'll be faster and denser.  I would    //  expect most of the states to have at least one transition.    stateMachineTable       = new EnumMapMap                           Transition>>(prototype);    for (TransitionsListNode cursor = transitionsListNode;         cursor != null;         cursor = cursor.next) { # 遍历applicableTransition链表并压栈      stack.push(cursor.transition);    }    while (!stack.isEmpty()) {      stack.pop().apply(this); # 取出元素,并apply。apply会把元素放入statemachinetable中    }  }

最后,StateMachineFactory还承担了本不该自己承担的重任:doTransition。是的,InternalStateMachine的doTransition方法竟然代理到了工厂类,有些匪夷所思。

04

总结

YARN中的许多功能模块都有较复杂的状态定义,状态机在其中起到了关键作用。这部分代码虽然实现方式给人一种绕来绕去的感觉,但整体逻辑还是比较易懂的。