Java:如何使用完成的多个线程中的第一个的结果?

时间:2016-12-06 00:49:29

标签: java multithreading concurrency

我在Java中遇到问题,我想同时生成多个并发线程。我想先使用任何线程/任务完成的结果,然后放弃/忽略其他线程/任务的结果。我在cancelling slower threads找到了类似的问题,但认为这个新问题不同,足以保证一个全新的问题。

请注意,我已根据我认为这个类似问题的最佳答案包含了以下答案,但将其更改为最适合这个新问题(虽然类似)。我想分享知识,看看是否有更好的方法来解决这个问题,因此下面的问题和自我回答。

2 个答案:

答案 0 :(得分:2)

您可以使用ExecutorService.invokeAny。从其文档:

  

执行给定的任务,返回已成功完成的任务的结果....正常或特殊退货后,未完成的任务将被取消。

答案 1 :(得分:0)

这个答案是基于@lreeder对问题“Java threads - close other threads when first thread completes”的回答。

基本上,我的回答与他的答案之间的区别在于他通过Semaphore关闭线程,我只是通过AtomicReference记录最快线程的结果。请注意,在我的代码中,我做了一些有点奇怪的事情。也就是说,我使用AtomicReference<Integer>的实例而不是更简单的AtomicInteger。我这样做,以便我可以比较并将值设置为空整数;我不能使用AtomicInteger的空整数。这允许我设置任何整数,而不仅仅是一组整数,不包括一些sentinel值。此外,还有一些不那么重要的细节,例如使用ExecutorService而不是显式线程,以及更改Worker.completed的设置方式,因为以前可能有多个线程可以先完成

public class ThreadController {
  public static void main(String[] args) throws Exception {
    new ThreadController().threadController();
  }

  public void threadController() throws Exception {
    int numWorkers = 100;

    List<Worker> workerList = new ArrayList<>(numWorkers);
    CountDownLatch startSignal = new CountDownLatch(1);
    CountDownLatch doneSignal = new CountDownLatch(1);
    //Semaphore prevents only one thread from completing
    //before they are counted
    AtomicReference<Integer> firstInt = new AtomicReference<Integer>();
    ExecutorService execSvc = Executors.newFixedThreadPool(numWorkers);

    for (int i = 0; i < numWorkers; i++) {
      Worker worker = new Worker(i, startSignal, doneSignal, firstInt);
      execSvc.submit(worker);
      workerList.add(worker);
    }

    //tell workers they can start
    startSignal.countDown();

    //wait for one thread to complete.
    doneSignal.await();

    //Look at all workers and find which one is done
    for (int i = 0; i < numWorkers; i++) {
      if (workerList.get(i).isCompleted()) {
        System.out.printf("Thread %d finished first, firstInt=%d\n", i, firstInt.get());
      }
    }
  }
}

class Worker implements Runnable {

  private final CountDownLatch startSignal;
  private final CountDownLatch doneSignal;
  // null when not yet set, not so for AtomicInteger
  private final AtomicReference<Integer> singleResult;
  private final int id;
  private boolean completed = false;

  public Worker(int id, CountDownLatch startSignal, CountDownLatch doneSignal, AtomicReference<Integer> singleResult) {
    this.id = id;
    this.startSignal = startSignal;
    this.doneSignal = doneSignal;
    this.singleResult = singleResult;
  }

  public boolean isCompleted() {
    return completed;
  }

  @Override
  public void run() {
    try {
      //block until controller counts down the latch
      startSignal.await();
      //simulate real work
      Thread.sleep((long) (Math.random() * 1000));

      //try to get the semaphore. Since there is only
      //one permit, the first worker to finish gets it,
      //and the rest will block.
      boolean finishedFirst = singleResult.compareAndSet(null, id);
      // only set this if the result was successfully set
      if (finishedFirst) {
        //Use a completed flag instead of Thread.isAlive because
        //even though countDown is the last thing in the run method,
        //the run method may not have before the time the 
        //controlling thread can check isAlive status
        completed = true;
      }
    }
    catch (InterruptedException e) {
      //don't care about this
    }
    //tell controller we are finished, if already there, do nothing
    doneSignal.countDown();
  }
}