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

posted @ 2017-03-26 16:21  -六月飞雪-  阅读(1950)  评论(0编辑  收藏  举报