Spring-statemachine Action不能并发执行的问题
Spring-statemachine版本:当前最新的1.2.3.RELEASE版本
这几天一直被Action是串行执行搞得很郁闷,写了一个demo专门用来测试:
public static void main(String[] args) throws Throwable {
StateMachineBuilder.Builder<String, String> builder = StateMachineBuilder.builder();
StaticListableBeanFactory beanFactory = new StaticListableBeanFactory();
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(30);
executor.setKeepAliveSeconds(30000);
executor.setMaxPoolSize(30);
executor.setQueueCapacity(3000);
executor.initialize();
builder.configureConfiguration()
.withConfiguration()
.beanFactory(beanFactory)
.taskExecutor(executor)
.taskScheduler(new ConcurrentTaskScheduler())
.listener(new StateMachineListenerAdapter<String, String>() {
@Override
public void stateEntered(State<String, String> state) {
String id = state == null ? null : state.getId();
System.out.println("entered " + id);
}
@Override
public void stateExited(State<String, String> state) {
String id = state == null ? null : state.getId();
System.out.println("exited " + id);
}
});
StateMachineStateConfigurer<String, String> smsConfigurer = builder.configureStates();
smsConfigurer.withStates()
.initial("READY")
.fork("FORK")
.state("TASKS")
.join("JOIN")
.choice("CHOICE")
.state("ERROR")
.and()
.withStates()
.parent("TASKS")
.initial("T1")
.end("T1E")
.and()
.withStates()
.parent("TASKS")
.initial("T2")
.end("T2E");
StateMachineTransitionConfigurer<String, String> smtConfigurer = builder.configureTransitions();
smtConfigurer.withExternal()
.source("READY").target("FORK")
.and()
.withFork()
.source("FORK").target("TASKS")
.and()
.withJoin()
.source("TASKS").target("JOIN")
.and()
.withExternal()
.source("T1").target("T1E")
.action(getAction())
.and()
.withExternal()
.source("T2").target("T2E")
.action(getAction())
.and()
.withExternal()
.source("JOIN").target("CHOICE")
.and()
.withChoice()
.source("CHOICE")
.first("ERROR", c -> true)
.last("READY");
StateMachine<String, String> stateMachine = builder.build();
stateMachine.start();
}
public static Action<String, String> getAction() {
return c -> {
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>..");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
;
}
System.out.println("<<<<<<<<<<<<<<<<<<<<<<<<<<..");
};
}
}
打出来的日志是
entered READY
exited READY
entered TASKS
entered T1
>>>>>>>>>>>>>>>>>>>>>>>>>>..
entered T2
<<<<<<<<<<<<<<<<<<<<<<<<<<..
exited T1
entered T1E
>>>>>>>>>>>>>>>>>>>>>>>>>>..
<<<<<<<<<<<<<<<<<<<<<<<<<<..
exited T2
entered T2E
exited TASKS
entered ERROR
如果是并发执行的话,应该像是这样:
>>>>>>>>>>>>>>>>>>>>>>>>>>..
>>>>>>>>>>>>>>>>>>>>>>>>>>..
<<<<<<<<<<<<<<<<<<<<<<<<<<..
<<<<<<<<<<<<<<<<<<<<<<<<<<..
然后趁着周末debug一发,跟着代码一步步走,发现org.springframework.statemachine.config.AbstractStateMachineFactory
类中有这段代码:
// 从所有region的transition和当前region的state中抽取出当前region的transition
//
// in代表所有的transition(包括父状态机),stateDatas代表当前region(子状态机)的状态
private Collection<TransitionData<S, E>> resolveTransitionData(Collection<TransitionData<S, E>> in, Collection<StateData<S, E>> stateDatas) {
ArrayList<TransitionData<S, E>> out = new ArrayList<TransitionData<S,E>>();
Collection<Object> states = new ArrayList<Object>();
for (StateData<S, E> stateData : stateDatas) {
states.add(stateData.getParent()); // 抽出父状态机的状态
}
for (TransitionData<S, E> transitionData : in) {
S state = transitionData.getState(); // 从下一行代码可以推理得出此处的getState得到的是父状态机的状态
if (state != null && states.contains(state)) { // 核心代码,如果父状态机的状态包含state,就加入到out去当作子状态机的transition,最后返回子状态机的transition集合
out.add(transitionData);
}
}
return out;
}
看到了吧,我当时猜测构建transition时还有一个state方法用来标识父状态机的状态,于是到withExternal()后面尝试看看有没有一个叫做state()的方法,结果还真有!
然后代码改成这样:
smtConfigurer
.withExternal()
.source("READY").target("FORK")
.and()
.withFork()
.source("FORK").target("TASKS")
.and()
.withJoin()
.source("TASKS").target("JOIN")
.and()
.withExternal()
.state("TASKS") // 增加本行设置父状态机的状态
.source("T1").target("T1E")
.action(getAction())
.and()
.withExternal()
.state("TASKS") // 增加本行设置父状态机的状态
.source("T2").target("T2E")
.action(getAction())
.and()
.withExternal()
.source("JOIN").target("CHOICE")
.and()
.withChoice()
.source("CHOICE")
.first("ERROR", c -> true)
.last("READY");
结果日志终于并发执行了,好开心啊~
entered READY
exited READY
entered TASKS
entered T2
entered T1
>>>>>>>>>>>>>>>>>>>>>>>>>>..
>>>>>>>>>>>>>>>>>>>>>>>>>>..
<<<<<<<<<<<<<<<<<<<<<<<<<<..
<<<<<<<<<<<<<<<<<<<<<<<<<<..
exited T2
exited T1
entered T2E
entered T1E
exited TASKS
entered SUCCESS
关于设置transition的state,官方的reference并没有相关的说明,所以初次使用spring-statemachine做并发任务状态管理的话,基本上都会遇到这个问题。
Github issue: https://github.com/spring-projects/spring-statemachine/issues/336