简体   繁体   中英

Efficient state machine pattern in java

I am writing a java simulation application which has a lot of entities to simulate. Each of these entities has a certain state at any time in the system. A possible and natural approach to model such an entity would be using the state (or state machine) pattern. The problem is that it creates a lot of objects during the runtime if there are a lot of state switches, what might cause bad system performance. What design alternatives do I have? I want performance to be the main criteria after maintainability.

Thanks

The below code will give you high performance (~10ns/event) zero runtime GC state machine implementation. Use explicit state machines whenever you have a concept of state in the system or component, this not only makes the code clean and scalable but also lets people (not even programmers) see immediately what the system does without having to dig in numerous callbacks:

abstract class Machine {
    enum State {
      ERROR,
      INITIAL,
      STATE_0,
      STATE_1,
      STATE_2;
    }

    enum Event {
      EVENT_0,
      EVENT_1,
      EVENT_2;
    }

    public static final int[][] fsm;
    static {
      fsm = new int[State.values().length][];
      for (State s: State.values()) {
        fsm[s.ordinal()] = new int[Event.values().length];
      }
    }

    protected State state = State.INITIAL;
    // child class constructor example
    // public Machine() {
    //   // specify allowed transitions
    //   fsm[State.INITIAL.ordinal()][Event.EVENT_0.ordinal()] = State.STATE_0.ordinal();
    //   fsm[State.STATE_0.ordinal()][Event.EVENT_0.ordinal()] = State.STATE_0.ordinal();
    //   fsm[State.STATE_0.ordinal()][Event.EVENT_1.ordinal()] = State.STATE_1.ordinal();
    //   fsm[State.STATE_1.ordinal()][Event.EVENT_1.ordinal()] = State.STATE_1.ordinal();
    //   fsm[State.STATE_1.ordinal()][Event.EVENT_2.ordinal()] = State.STATE_2.ordinal();
    //   fsm[State.STATE_1.ordinal()][Event.EVENT_0.ordinal()] = State.STATE_0.ordinal();
    //   fsm[State.STATE_2.ordinal()][Event.EVENT_2.ordinal()] = State.STATE_2.ordinal();
    //   fsm[State.STATE_2.ordinal()][Event.EVENT_1.ordinal()] = State.STATE_1.ordinal();
    //   fsm[State.STATE_2.ordinal()][Event.EVENT_0.ordinal()] = State.STATE_0.ordinal();
    // }

    public final void onEvent(Event event) {
      final State next = State.values()[ fsm[state.ordinal()][event.ordinal()] ];
      if (next ==  State.ERROR) throw new RuntimeException("invalid state transition");
      if (acceptEvent(event)) {
        final State prev = state;
        state = next;
        handleEvent(prev, event);
      }
    }

    public abstract boolean acceptEvent(Event event);
    public abstract void handleEvent(State prev, Event event);
}

if fsm is replaced with a unidimentional array of size S*E it will also improve cache proximity characteristics of the state machine.

My suggestion:
Have you "transitions managment" be configurable (ie - via XML).

Load the XML to a repository holding the states.
The internal data structure will be a Map:

Map<String,Map<String,Pair<String,StateChangeHandler>>> transitions;

The reason for my selection is that this will be a map from a state name
To a map of "inputs" and new states:
Each map defines a map between possible input and the new state it leads to which is defined by the state name and a StateChangeHandler I will elaborate on later
change state method at the repository would have a signature of:

void changeState(StateOwner owner, String input)

This way the repository is stateless in the sense of the state owner using it, you can copy one copy,
and not worry about thread safety issues.
StateOwner will be an interface your Classes that need state changing should implement.
I think the interface should look like this:

public interace StateOwner {
   String getState();
   void String setState(String newState);
}

In addition, you will have a ChangeStateHandler interface:

public interface StateChangeHandler {
    void onChangeState(StateOwner, String newState) {
    }
}

When the repository's changeState method is called, it will
check at the data structure that the current state of the stateOwner has a map of "inputs". If it has such a map, it will check if the input has a new State to change to, and invoke the onChangeState method.
I will suggest you have a default implementation of the StateChangeHandler, and of course sub classes that will define the state change behavior more explicitly.

As I previously mentioned, all this can be loaded from an XML configuration, and using reflection you can instantitate StateChangeHandler objects based on their name (as mentioned at the XML) and that will be held in the repository.


Efficiency and good performance rely and obtained using the following points:
a. The repository itself is stateless - no internal references of StateOwner should be kept.
b. You load the XML once , when the system starts, after that you should work with in memory data structure.
c. You will provide specific StateChangeHandler implementation only when needed, the default implementation should do basicaly nothing.
d. No need to instantiate new objects of Handlers (as they should be stateless)

This proposal isn't universal, it isn't UML compliant but for simple thing, it's a simple mean .

import java.util.HashMap;
import java.util.Map;

class Mobile1
{
   enum State {
      FIRST, SECOND, THIRD
   }

   enum Event {
      FIRST, SECOND, THIRD
   }

   public Mobile1() {       // initialization may be done by loading a file
      Map< Event, State > tr;
      tr = new HashMap<>();
      tr.put( Event.FIRST, State.SECOND );
      _fsm.put( State.FIRST, tr );
      tr = new HashMap<>();
      tr.put( Event.SECOND, State.THIRD );
      _fsm.put( State.SECOND, tr );
      tr = new HashMap<>();
      tr.put( Event.THIRD, State.FIRST );
      _fsm.put( State.THIRD, tr );
   }

   public void activity() {        // May be a long process, generating events,
      System.err.println( _state );// to opposite to "action()" see below
   }

   public void handleEvent( Event event ) {
      Map< Event, State > trs = _fsm.get( _state );
      if( trs != null ) {
         State futur = trs.get( event );
         if( futur != null ) {
            _state = futur;
           // here we may call "action()" a small piece of code executed
           // once per transition
         }
      }
   }

   private final Map<
      State, Map<
         Event, State >> _fsm   = new HashMap<>();
   private /* */ State   _state = State.FIRST;
}

public class FSM_Test {
   public static void main( String[] args ) {
      Mobile1 m1 = new Mobile1();
      m1.activity();
      m1.handleEvent( Mobile1.Event.FIRST );
      m1.activity();
      m1.handleEvent( Mobile1.Event.SECOND );
      m1.activity();
      m1.handleEvent( Mobile1.Event.FIRST );   // Event not handled
      m1.activity();
      m1.handleEvent( Mobile1.Event.THIRD );
      m1.activity();
   }
}

output:

FIRST
SECOND
THIRD
THIRD
FIRST

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM