“状态机四要素:现态,条件,动作,次态”
状态机,通常被用来管理一个实体的生命周期。通过它,可以使复杂的状态转化问题模式化,达到代码高内聚的效果。事实上,状态机也确实是一种设计模式。
状态机有四个要素:
- 现态:实体的当前状态
- 次态:实体的下个状态
- 条件:引发实体状态转移的条件
- 动作:条件发生时,伴随的动作
示意图如下:
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。
这两个都是接口类,其实并没有什么类图关系。放在一起主要是想指出两点。
第一,为什么要从概念上区分两者,差别在哪里。对于SingleArc来说,它的现态和次态是单一确定的;但是对于MultipleArc来说,它的次态则可以有多个。transition的方法的返回值其实也暗示了这点。前者返回void,因为状态关系明确;后者返回STATE,需要指明究竟是多个次态中的哪一个。真正能够说明两者区别的代码在StateMachineFactory中,它也是本文的唯一关键类,我们放在关键类分析中细讲。
第二,前文提到,状态机的四要素是现态,次态,条件,动作。而实际上,我们transition方法,其实只包含了两项,即条件(对应event)和动作(对应方法本身),第一个参数operand是状态机关联的实体。那么为什么没有包含现态和次态两项呢?一个揣测是,接口设计者的本意是通过operand来访问两个状态。
但是,StateMachineFactory的实现者似乎不喜欢这个不透明的接口,所以之后分析该类代码时,我们会发现其内部,又定义了一个Transition接口,及其实现 SingleInternalArc和MultipleInternalArc,两者分别对原始接口进行了封装。
再来看StateMachine,它也是一个接口类,类图如下:
最后是StateMachineFactory,是一个实现类。其内部又定义了一些接口及实现,类图如下:
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中的许多功能模块都有较复杂的状态定义,状态机在其中起到了关键作用。这部分代码虽然实现方式给人一种绕来绕去的感觉,但整体逻辑还是比较易懂的。