在超时后执行 returns 的长时间计算

Performing a long calculation that returns after a timeout

我想使用迭代加深执行搜索,这意味着每次我这样做时,我都会更深入并且需要更长的时间。有时间限制(2 秒)以获得最佳结果。根据我的研究,执行此操作的最佳方法是使用 ExecutorService、Future 并在时间用完时中断它。这是我目前拥有的:

在我的主要功能中:

ExecutorService service = Executors.newSingleThreadExecutor();
ab = new AB();
Future<Integer> f = service.submit(ab);
Integer x = 0;
try {
    x = f.get(1990, TimeUnit.MILLISECONDS);
}
catch(TimeoutException e) {
    System.out.println("cancelling future");
    f.cancel(true);
}
catch(Exception e) {
    throw new RuntimeException(e);
}
finally {
    service.shutdown();
}
System.out.println(x);

和可调用对象:

public class AB implements Callable<Integer> {

    public AB() {}

    public Integer call() throws Exception {
        Integer x = 0;
        int i = 0;
        while (!Thread.interrupted()) {
            x = doLongComputation(i);
            i++;
        }
        return x;
    }
}

我有两个问题:

  1. doLongComputation() 未被中断,程序仅在完成工作后检查 Thread.interrupted() 是否为真。我是否需要在 doLongComputation() 中检查线程是否已被中断?
  2. 即使我摆脱了 doLongComputation(),主要方法也没有接收到 x 的值。我如何确保我的程序等待 Callable 到 "clean up" 和 return 迄今为止最好的 x?

回答第 1 部分:是的,您需要让您的长任务检查中断标志。打断需要被打断任务的配合

另外你应该使用Thread.currentThread().isInterrupted()除非你特别想清除中断标志。抛出(或重新抛出)InterruptedException 的代码使用 Thread#interrupted 作为检查标志和清除标志的便捷方式,当您编写 Runnable 或 Callable 时,这通常不是您想要的。

现在回答第 2 部分:取消不是您想要的。

使用取消来停止计算,return 中间结果不起作用,一旦取消未来,就无法从 get 方法中检索 return 值。您可以做的是将计算的每个细化作为自己的任务,这样您就可以提交一个任务,获得结果,然后使用该结果作为起点提交下一个任务,同时保存最新的结果。

这是我想出的一个例子来证明这一点,使用牛顿法计算平方根的逐次逼近。每次迭代都是一个单独的任务,在前一个任务完成时提交(使用前一个任务的近似值):

import java.util.concurrent.*;
import java.math.*;

public class IterativeCalculation {

    static class SqrtResult {
        public final BigDecimal value;
        public final Future<SqrtResult> next;
        public SqrtResult(BigDecimal value, Future<SqrtResult> next) {
            this.value = value;
            this.next = next;
        }
    }

    static class SqrtIteration implements Callable<SqrtResult> {
        private final BigDecimal x;
        private final BigDecimal guess;
        private final ExecutorService xs;
        public SqrtIteration(BigDecimal x, BigDecimal guess, ExecutorService xs) {
            this.x = x;
            this.guess = guess; 
            this.xs = xs;
        }

        public SqrtResult call() {
            BigDecimal nextGuess = guess.subtract(guess.pow(2).subtract(x).divide(new BigDecimal(2).multiply(guess), RoundingMode.HALF_EVEN));
            return new SqrtResult(nextGuess, xs.submit(new SqrtIteration(x, nextGuess, xs)));
        }
    }

    public static void main(String[] args) throws Exception {
        long timeLimit = 10000L;
        ExecutorService xs = Executors.newSingleThreadExecutor();
        try {
            long startTime = System.currentTimeMillis();
            Future<SqrtResult> f = xs.submit(new SqrtIteration(new BigDecimal("612.00"), new BigDecimal("10.00"), xs));
            for (int i = 0; System.currentTimeMillis() - startTime < timeLimit; i++) {
                f = f.get().next;                
                System.out.println("iteration=" + i + ", value=" + f.get().value);
            }
            f.cancel(true);
        } finally {
            xs.shutdown();
        }
    }
}