Java 如何使用CompletableFuture而不冒堆栈溢出错误的风险?

Java 如何使用CompletableFuture而不冒堆栈溢出错误的风险?,java,java-8,stack-overflow,completable-future,Java,Java 8,Stack Overflow,Completable Future,我想遍历异步函数的搜索空间。我将逻辑编码如下: /** * Assuming that a function maps a range of inputs to the same output value, minimizes the input value while * maintaining the output value. * * @param previousInput the last input known to return {@code target} * @par

我想遍历异步函数的搜索空间。我将逻辑编码如下:

/**
 * Assuming that a function maps a range of inputs to the same output value, minimizes the input value while
 * maintaining the output value.
 *
 * @param previousInput the last input known to return {@code target}
 * @param currentInput  the new input value to evaluate
 * @param function      maps an input to an output value
 * @param target        the expected output value
 * @return the minimum input value that results in the {@code target} output value
 * <br>{@code @throws NullPointerException} if any argument is null
 * <br>{@code @throws IllegalArgumentException} if {@code stepSize} is zero}
 */
private static CompletionStage<BigDecimal> optimizeInput(BigDecimal previousInput,
                                                         BigDecimal currentInput,
                                                         BigDecimal stepSize,
                                                         Function<BigDecimal, CompletionStage<BigDecimal>> function,
                                                         BigDecimal target)
{
    return function.apply(currentInput).thenCompose(output ->
    {
        assertThat("stepSize", stepSize).isNotZero();
        int outputMinusTarget = output.compareTo(target);
        if (outputMinusTarget != 0)
            return CompletableFuture.completedFuture(previousInput);
        BigDecimal nextInput = currentInput.add(stepSize);
        if (nextInput.compareTo(BigDecimal.ZERO) < 0)
            return CompletableFuture.completedFuture(previousInput);
        return optimizeInput(currentInput, nextInput, stepSize, function, target);
    });
}
/**
*假设函数将一系列输入映射到相同的输出值,则在
*保持输出值。
*
*@param previousInput已知返回{@code target}的最后一个输入
*@param currentInput要计算的新输入值
*@param函数将输入映射到输出值
*@param目标为预期的输出值
*@返回导致{@code target}输出值的最小输入值
*
{@code@throws NullPointerException}如果任何参数为null *
{@code@throws IllegalArgumentException}如果{@code stepSize}为零} */ 私有静态CompletionStage optimizeInput(BigDecimal previousInput, 大十进制电流输入, 大十进制步长, 功能, 大十进制目标) { 返回函数。应用(当前输入)。然后合成(输出-> { 断言(“步长”,步长).isNotZero(); int outputMinusTarget=output.compareTo(目标); 如果(outputMinusTarget!=0) 返回CompletableFuture.completedFuture(以前的输入); BigDecimal nextInput=currentInput.add(步长); if(nextInput.compareTo(BigDecimal.ZERO)<0) 返回CompletableFuture.completedFuture(以前的输入); 返回优化输入(currentInput、nextInput、步长、函数、目标); }); }

不幸的是,如果函数有一个大的搜索空间,那么在一些迭代之后会引发StackOverflower错误。是否可以使用固定大小的堆栈迭代遍历搜索空间?

您有以下递归结构

CompletableFuture<T> compute(...) {
  return asyncTask().thenCompose(t -> {
    if (...)
      return completedFuture(t);
    } else {
      return compute(...);
    }
  }
}
CompletableFuture计算(…){
返回asyncTask()。然后组合(t->{
如果(…)
返回已完成的未来(t);
}否则{
返回计算(…);
}
}
}
您可以重写它,以避免将来可完成的合成及其在完成期间的堆栈使用

CompletableFuture<T> compute(...) {
  CompletableFuture<T> result = new CompletableFuture<>();
  computeHelper(result, ...);
  return result;
}   

void computeHelper(CompletableFuture<T> result, ...) {
  asyncTask().thenAccept(t -> {
    if (...) {
      result.complete(t);
    } else {
      computeHelper(result, ...);
    }
  });
}
CompletableFuture计算(…){
CompletableFuture结果=新建CompletableFuture();
computeHelper(结果,…);
返回结果;
}   
作废computeHelper(CompletableFuture结果,…){
asyncTask()。然后接受(t->{
如果(…){
结果:完全(t);
}否则{
computeHelper(结果,…);
}
});
}

如果
asyncTask()
不是真正的异步,只使用当前线程,您必须用它的一个异步版本替换
然后Accept
,以使用executor任务队列而不是线程堆栈。

dfogni的答案应该可以很好地工作——但为了完整性,在method是使用类型技术同步的

为了让它更简单,我引入了一个类来捕获迭代之间变化的状态,并引入了实现完成检查并生成下一个状态的方法。我相信这与原始逻辑相同,但您可以进行三重检查

private static CompletionStage<BigDecimal> optimizeInput(BigDecimal previousInput,
                                                          BigDecimal currentInput,
                                                          BigDecimal stepSize,
                                                          Function<BigDecimal, CompletionStage<BigDecimal>> function,
                                                          BigDecimal target) {
    class State {
        BigDecimal prev;
        BigDecimal curr;
        BigDecimal output;

        State(BigDecimal prev, BigDecimal curr, BigDecimal output) {
            this.prev = prev;
            this.curr = curr;
            this.output = output;
        }

        boolean shouldContinue() {
            return output.compareTo(target) == 0 && curr.add(stepSize).compareTo(BigDecimal.ZERO) >= 0;
        }

        CompletionStage<State> next() {
            BigDecimal nextInput = curr.add(stepSize);
            return function.apply(nextInput).thenApply(nextOutput -> new State(curr, nextInput, nextOutput));
        }
    }

    /* Now it gets complicated... we have to check if we're running on the same thread we were called on. If we
     * were, instead of recursively calling `next()`, we'll use PassBack to pass our new state back 
     * to the stack that called us.
     */
    class Passback {
        State state = null;
        boolean isRunning = true;

        State poll() {
            final State c = this.state;
            this.state = null;
            return c;
        }
    }
    class InputOptimizer extends CompletableFuture<BigDecimal> {
        void optimize(State state, final Thread previousThread, final Passback previousPassback) {
            final Thread currentThread = Thread.currentThread();

            if (currentThread.equals(previousThread) && previousPassback.isRunning) {
                // this is a recursive call, our caller will run it
                previousPassback.state = state;
            } else {
                Passback passback = new Passback();
                State curr = state;
                do {
                    if (curr.shouldContinue()) {
                        curr.next().thenAccept(next -> optimize(next, currentThread, passback));
                    } else {
                        complete(curr.prev);
                        return;
                    }
                // loop as long as we're making synchronous recursive calls
                } while ((curr = passback.poll()) != null);
                passback.isRunning = false;
            }
        }
    }

    InputOptimizer ret = new InputOptimizer();
    function.apply(currentInput)
            .thenAccept(output -> ret.optimize(
                    new State(previousInput, currentInput, output),
                    null, null));
    return ret;
}
private static CompletionStage optimizeInput(BigDecimal-previousInput,
大十进制电流输入,
大十进制步长,
功能,
大十进制目标){
阶级国家{
BigDecimal-prev;
大十进制货币;
大十进制输出;
状态(BigDecimal prev、BigDecimal curr、BigDecimal output){
this.prev=prev;
this.curr=curr;
这个。输出=输出;
}
boolean shouldContinue(){
返回output.compareTo(target)==0&&curr.add(步长).compareTo(BigDecimal.ZERO)>=0;
}
CompletionStage下一步(){
BigDecimal nextInput=当前添加(步长);
返回函数.apply(nextInput).然后apply(nextOutput->新状态(curr,nextInput,nextOutput));
}
}
/*现在它变得复杂了…我们必须检查我们是否在调用的同一线程上运行。如果我们
*我们将使用PassBack将新状态传回,而不是递归调用'next()`
*到调用我们的堆栈。
*/
类回传{
State=null;
布尔值isRunning=true;
州民意测验(){
最终状态c=该状态;
this.state=null;
返回c;
}
}
类InputOptimizer扩展了CompletableFuture{
void优化(状态状态、最终线程previousThread、最终回传previousPassback){
最终线程currentThread=Thread.currentThread();
if(currentThread.equals(previousThread)&&previousPassback.isRunning){
//这是一个递归调用,调用方将运行它
previousPassback.state=状态;
}否则{
Passback Passback=新Passback();
状态电流=状态;
做{
如果(当前应继续()){
curr.next().thenAccept(下一步->优化(下一步,currentThread,passback));
}否则{
已完成(当前版本);
返回;
}
//循环,只要我们进行同步递归调用
}while((curr=passback.poll())!=null);
passback.isRunning=false;
}
}
}
InputOptimizer ret=新的InputOptimizer();
函数。应用(currentInput)
.然后接受(输出->重新优化(
新国家(p
 private static CompletionStage<BigDecimal> optimizeInput(BigDecimal previousInput,
                                                          BigDecimal currentInput,
                                                          BigDecimal stepSize,
                                                          Function<BigDecimal, CompletionStage<BigDecimal>> function,
                                                          BigDecimal target) {
    // ... State class from before
    return function
            .apply(currentInput)
            .thenCompose(output -> AsyncTrampoline.asyncWhile(
                    State::shouldContinue, 
                    State::next, 
                    new State(previousInput, currentInput, output)))
            .thenApply(state -> state.prev);    
}