简体   繁体   中英

Java: How do I use the result of the first of multiple threads that complete?

I have a problem in Java where I want to spawn multiple concurrent threads simultaneously. I want to use the result of whichever thread/task finishes first, and abandon/ignore the results of the other threads/tasks. I found a similar question for just cancelling slower threads but thought that this new question was different enough to warrant an entirely new question.

Note that I have included an answer below based what I considered to be the best answer from this similar question but changed it to best fit this new (albeit similar) problem. I wanted to share the knowledge and see if there is a better way of solving this problem, hence the question and self-answer below.

You can use ExecutorService.invokeAny . From its documentation:

Executes the given tasks, returning the result of one that has completed successfully …. Upon normal or exceptional return, tasks that have not completed are cancelled.

This answer is based off @lreeder's answer to the question " Java threads - close other threads when first thread completes ".

Basically, the difference between my answer and his answer is that he closes the threads via a Semaphore and I just record the result of the fastest thread via an AtomicReference . Note that in my code, I do something a little weird. Namely, I use an instance of AtomicReference<Integer> instead of the simpler AtomicInteger . I do this so that I can compare and set the value to a null integer; I can't use null integers with AtomicInteger . This allows me to set any integer, not just a set of integers, excluding some sentinel value. Also, there are a few less important details like the use of an ExecutorService instead of explicit threads, and the changing of how Worker.completed is set, because previously it was possible that more than one thread could finish first .

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();
  }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM