私は数十万のリスト持っているCallable
オブジェクトを。それぞれが実行されると、それに与えられた値に基づいて、潜在的に長い計算を行います。このため、私は、(好ましくはエグゼキュータのいくつかの種類を使用して)非同期で各タスクを実行し、時間内に完了しないものをキャンセルし、30秒後に各計算の結果を取得したいです。(得られた値を他の場所で使用されています。)
これは私がこれまでのところ、それを実装しました方法です。
private void process() {
class Runner implements Callable<BigDecimal> {
final int x, y;
Runner(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public BigDecimal call() {
BigDecimal gWidth = xMax.subtract(xMin), gHeight = yMax.subtract(yMin);
BigDecimal gy = gHeight.multiply(BigDecimal.valueOf(-y)).divide(BigDecimal.valueOf(height)).add(yMax);
BigDecimal gx = gWidth.multiply(BigDecimal.valueOf(x)).divide(BigDecimal.valueOf(width)).add(xMin);
// The calculation begins when this method is called
BigDecimal result = calculateAt(gx, gy);
return result;
}
}
ExecutorService exec = Executors.newCachedThreadPool();
List<Runner> runners = new ArrayList<>();
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
runners.add(new Runner(x, y));
}
}
try {
List<Future<BigDecimal>> results = exec.invokeAll(runners, 30, TimeUnit.SECONDS);
for (Future<BigDecimal> future : results) {
// Check if the future's task was cancelled and process the results
}
} catch (InterruptedException | ExecutionException ex) {
ex.printStackTrace();
}
exec.shutdown();
}
// Extra variables and methods
BigDecimal xMin = BigDecimal.valueOf(-7),
xMax = BigDecimal.valueOf(7),
yMin = BigDecimal.valueOf(-7),
yMax = BigDecimal.valueOf(7);
int height = 850, width = 850;
private BigDecimal calculateAt(BigDecimal x, BigDecimal y) {
try {
// Just to simulate a potential execution time
Thread.sleep((ThreadLocalRandom.current().nextInt(45) + 1) * 1000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
return BigDecimal.ONE;
}
ArrayListには、runners
各格納Callable
そしてすべてのタスクを実行するExecutorServiceのに送信され、実行されます。私はに実行している問題は、タスクを同期的に開始しているように見えるということで、30秒のタイムアウトの後、最初の40か5万タスクは、はるかに少ない実行を開始し、終了しました。
何が起こっているように見えることはということですExecutorService.invokeAll
方法はのための唯一の30秒のウィンドウ許可されたすべての起動するリスト内のタスクと実行を終了します。私が代わりに必要なのはつまり、フィニッシュにタスクのための30秒を許可し、タスクごとに開始するには、この30秒のウィンドウのためであることがすでに始まっています一度。 invokeAll
少なくともないで、これを実行していないようですnewCachedThreadPool
。Javaライブラリやない実装の他の方法はありますか?
私はあなたの問題を解決することができると思いますCompletableFuture
。
例えば、ここで問題からの抜粋に基づいたコードは次のとおりです。
private static final ExecutorService executor = Executors.newCachedThreadPool();
private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(100);
private static void processAsync() {
List<CompletableFuture<Object>> futureList = IntStream.range(0, height).boxed()
.flatMap(y -> IntStream.range(0, width).boxed().map(x -> new Runner(x, y)))
.map(runner ->
CompletableFuture.anyOf(
CompletableFuture.supplyAsync(runner, executor),
timeout(Duration.ofSeconds(30))
).exceptionally(throwable -> {
// timeout is handled here
return BigDecimal.ZERO;
})
)
.collect(Collectors.toList());
CompletableFuture.allOf(futureList.toArray(new CompletableFuture<?>[0]))
.thenAccept(v -> {
List<BigDecimal> results = futureList.stream()
.map(CompletableFuture::join)
.map(r -> (BigDecimal) r)
.collect(Collectors.toList());
// process final results
BigDecimal sum = results.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
System.out.println("Final sum: " + sum);
})
.exceptionally(throwable -> {
System.out.println("Failed allOf with " + throwable);
return null;
});
}
private static CompletableFuture<BigDecimal> timeout(Duration duration) {
CompletableFuture<BigDecimal> future = new CompletableFuture<>();
scheduler.schedule(
() -> future.completeExceptionally(new TimeoutException("Timeout " + Thread.currentThread().getName())), duration.toMillis(), MILLISECONDS);
return future;
}
private static class Runner implements Supplier<BigDecimal> {...
ここでの主なアイデアは、使用することですCompletableFuture.anyOf
し、それを適用しCompletableFuture
、あなたの役に立つタスクのとCopmpletableFuture
タイムアウトタスクの。タイムアウトを使用することによって達成されるScheduledExecutorService
とCompletableFuture.completeExceptionally
。だから、基本的には、anyOf
リターンはどちらか結果かTimeoutException
。