Java中返回第一个结束任务的任务执行器

Java中返回第一个结束任务的任务执行器,java,concurrency,threadpool,rx-java,reactive-programming,Java,Concurrency,Threadpool,Rx Java,Reactive Programming,我想实现一些东西,它将获得一个worker(callables)集合,在线程池上并行运行,当最快的worker返回结果时,优雅地关闭(ExecutorService.shutdownNow)其他worker,以避免浪费更多资源。如果所有worker都以异常结束,我需要重新显示最重要的异常(worker引发的所有异常都与重要性值关联)。此外,我需要有一个对整个执行器超时,这将终止所有工人,如果他们运行太长时间 我曾经考虑过使用RxJava来实现这一点,因为我觉得在这里可以实现一个简洁而漂亮的解决方

我想实现一些东西,它将获得一个worker(callables)集合,在线程池上并行运行,当最快的worker返回结果时,优雅地关闭(ExecutorService.shutdownNow)其他worker,以避免浪费更多资源。如果所有worker都以异常结束,我需要重新显示最重要的异常(worker引发的所有异常都与
重要性
值关联)。此外,我需要有一个对整个执行器超时,这将终止所有工人,如果他们运行太长时间

我曾经考虑过使用RxJava来实现这一点,因为我觉得在这里可以实现一个简洁而漂亮的解决方案。但也许你可以想出一些更好的工具(CompletableFutures,ForkJoinTasks?)。下面是我已经编写的代码,但它远不是一个有效的解决方案(我对反应式编程没有真正的经验,因此很难做到这一点):


public T run(Collection这是一个非常有趣的技术挑战,因此感谢您的提问。下面是一个针对Java8使用
CompletableFuture
的解决方案。在Java7中,您可以完全相同的方式使用
io.netty.util.concurrent.Promise

最简单的部分是处理正常情况:

  • 创造一个完整的未来
  • 安排任务
  • 回归未来
  • 第一个完成的将完成未来,其他的将被忽略(如果未终止,则原子布尔控件不会覆盖该值)
  • 在未来的下一阶段关闭executor服务
更棘手的部分是,当每一个抛出保持相同的逻辑流时,异常完成。这可以通过累积所有异常来解决,并在计数达到最后一个失败作业的作业计数时异常完成未来。传递的异常是按秩排序的列表中的第一个异常(这里将是最小的等级,相应地更改)。调用
future.get()
并将异常包装到
ExecutionException
中时,异常将可用

最后,因为您将获得一个未来,所以可以将超时值传递到
get
方法中

下面是实际的工作解决方案、异常类和测试:

public <R> CompletableFuture<R> execute(Collection<? extends Callable<R>> jobs) {
  final CompletableFuture<R> result = new CompletableFuture<>();
  if (jobs == null || jobs.isEmpty()) {
    result.completeExceptionally(new IllegalArgumentException("there must be at least one job"));
    return result;
  }
  final ExecutorService service = Executors.newFixedThreadPool(jobs.size());

  // accumulate all exceptions to rank later (only if all throw)
  final List<RankedException> exceptions = Collections.synchronizedList(Lists.newArrayList());
  final AtomicBoolean done = new AtomicBoolean(false);

  for (Callable<R> job: jobs) {
    service.execute(() -> {
      try {
        // this is where the actual work is done
        R res = job.call();
        // set result if still unset
        if (done.compareAndSet(false, true)) {
          // complete the future, move to service shutdown
          result.complete(res);
        }
      // beware of catching Exception, change to your own checked type
      } catch (Exception ex) {
        if (ex instanceof RankedException) {
          exceptions.add((RankedException) ex);
        } else {
          exceptions.add(new RankedException(ex));
        }
        if (exceptions.size() >= jobs.size()) {
          // the last to throw and only if all have thrown will run:
          Collections.sort(exceptions, (left, right) -> Integer.compare(left.rank, right.rank));
          // complete the future, move to service shutdown
          result.completeExceptionally(exceptions.get(0));
        }
      }
    });
  }
  // shutdown also on error, do not wait for this stage
  result.whenCompleteAsync((action, t) -> service.shutdownNow());
  return result;
}
现在有两个测试,成功和失败案例(稍微简化,但仍然):

@规则
public ExpectedException exception=ExpectedException.none();
私有静态类TestJob实现了可调用{
私有最终整数指数;
私人最终积分失败计数;
TestJob(int索引、int failOnCount){
这个指数=指数;
this.failOnCount=failOnCount;
}
@凌驾
public Double call()引发RankedException{
双res=0;
int count=(int)(Math.random()*1e6)+1;
如果(计数>故障计数){
抛出新的RankedException(计数,新运行时异常(“作业”+索引+“失败”));
}
for(int i=0;i
最后,测试运行的截断输出:

信息:使用115863.20802680103完成成功测试

严重:失败测试等级:388150

进程已完成,退出代码为0

注意:您可能需要通过
AtomicBoolean
发出进一步的信号,以便在第一个线程准备就绪时实际通知所有线程终止


我不能保证上面的代码没有错误,因为它是在匆忙中完成的,测试是初步的。它旨在指示进一步挖掘的方向。

您研究过RxJava操作符吗?您需要验证它在第一次完成时是否完成,但是,因为文档没有说明这一点。

PeRxJava的rfect应用程序。若要获得并行操作,请在
flatMap
中使用
flatMap
subscribeOn
。若要拾取错误,请使用
materialize
并在成功返回值后立即停止,请使用
takeUntil
。根据超时要求使用
timeout
操作符

ExecutorService executorService =
    Executors.newFixedThreadPool(workers.size());
Scheduler scheduler = Schedulers.from(executorService);
return Observable
    .from(workers)
    .flatMap(worker -> 
         Observable.fromCallable(worker)
             .subscribeOn(scheduler)
             .materialize())
    .takeUntil(notification -> notification.hasValue())
    .toList() 
    .timeout(30, TimeUnit.SECONDS)
    .flatMap(
        list -> {
            Notification<T> last = list.get(list.size() - 1);
            if (last.hasValue()) 
                return Observable.just(last.getValue());
            else {
                // TODO get the error notification from the list 
                // with the highest importance and emit
                return Observable.<T>error(err);
            }
        }).subscribe(subscriber);
executor服务executor服务=
Executors.newFixedThreadPool(workers.size());
调度器调度器=调度器。从(executorService);
可观测回波
.来自(工人)
.flatMap(工人->
可观察。从可调用(工作者)
.subscribeOn(调度程序)
.materialize())
.takeUntil(通知->通知.hasValue())
托利斯先生()
.超时(30,时间单位为秒)
.平面图(
列表->{
最后通知=list.get(list.size()-1);
if(last.hasValue())
返回Observable.just(last.getValue());
否则{
//TODO从列表中获取错误通知
//具有最高的重要性和排放量
返回可观测误差(err);
}
}).认购(认购人);
您是否知道“最快的”可能不是花费最少时间完成的?这完全取决于日程安排。那么,您到底想做什么?您是新来的,所以我不会做
public static class RankedException extends Exception {
  private final int rank;

  public RankedException(Throwable t) {
    this(0, t);
  }

  public RankedException(int rank, Throwable t) {
    super(t);
    this.rank = rank;
  }
}
@Rule
public ExpectedException exception = ExpectedException.none();

private static class TestJob implements Callable<Double> {
  private final int index;
  private final int failOnCount;

  TestJob(int index, int failOnCount) {
    this.index = index;
    this.failOnCount = failOnCount;
  }

  @Override
  public Double call() throws RankedException {
    double res = 0;
    int count = (int) (Math.random() * 1e6) + 1;
    if (count > failOnCount) {
      throw new RankedException(count, new RuntimeException("job " + index + " failed"));
    }
    for (int i = 0; i < count; i++) {
      res += Math.random();
    }
    return res;
  }
}

@Test
public void test_success() throws Exception {
  List<TestJob> jobs = Lists.newArrayList();
  for (int i = 0; i < 10; i++) {
    jobs.add(new TestJob(i, (int)(5*1e5))); // 50% should be alright
  }
  CompletableFuture<Double> res = execute(jobs);
  logger.info("SUCCESS-TEST completed with " + res.get(30, TimeUnit.SECONDS));
}

@Test
public void test_failure() throws Exception {
  List<TestJob> jobs = Lists.newArrayList();
  for (int i = 0; i < 10; i++) {
    jobs.add(new TestJob(i, 0)); // all should fail
  }
  CompletableFuture<Double> res = execute(jobs);
  exception.expect(ExecutionException.class);
  try {
    res.get(30, TimeUnit.SECONDS);
  } catch (ExecutionException ex) {
    logger.severe(String.format("FAIL-TEST rank: %s", ((RankedException) ex.getCause()).rank));
    throw ex;
  }
}
ExecutorService executorService =
    Executors.newFixedThreadPool(workers.size());
Scheduler scheduler = Schedulers.from(executorService);
return Observable
    .from(workers)
    .flatMap(worker -> 
         Observable.fromCallable(worker)
             .subscribeOn(scheduler)
             .materialize())
    .takeUntil(notification -> notification.hasValue())
    .toList() 
    .timeout(30, TimeUnit.SECONDS)
    .flatMap(
        list -> {
            Notification<T> last = list.get(list.size() - 1);
            if (last.hasValue()) 
                return Observable.just(last.getValue());
            else {
                // TODO get the error notification from the list 
                // with the highest importance and emit
                return Observable.<T>error(err);
            }
        }).subscribe(subscriber);