2016-12-06 3 views
0

У меня проблема на Java, где я хочу одновременно создавать несколько одновременных потоков. Я хочу использовать результат того, что нить/задача заканчивается в первую очередь, и отказаться/игнорировать результаты других потоков/задач. Я нашел аналогичный вопрос только для cancelling slower threads, но думал, что этот новый вопрос был достаточно разным, чтобы гарантировать совершенно новый вопрос.Java: Как я могу использовать результат первого из нескольких потоков?

Обратите внимание, что я включил ответ ниже, основываясь на том, что я считаю лучшим ответом на этот похожий вопрос, но изменил его, чтобы лучше всего соответствовать этой новой (хотя и аналогичной) проблеме. Я хотел поделиться знаниями и посмотреть, есть ли лучший способ решить эту проблему, поэтому вопрос и ответ на вопрос ниже.

+2

Есть ли причина, чтобы не использовать [ExecutorService.invokeAny] (https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html#invokeAny-java. util.Collection-)? – VGR

+0

@ VGR Я просто не знал, что этот метод существует, но именно поэтому я написал этот вопрос. Я просто попробовал новый код с помощью «ExecutorService.invokeAny()», и он значительно упрощает код. Если вы добавите ответ, указывающий на это, я буду рад проголосовать за него и принять его. Огромное спасибо! – entpnerd

ответ

2

Вы можете использовать ExecutorService.invokeAny. Из его документации:

Выполняет заданные задачи, возвращая результат успешно завершенной работы. При нормальном или исключительном возврате задачи, которые не были выполнены, отменены.

0

Этот ответ основан на ответе @ lreeder на вопрос «Java threads - close other threads when first thread completes».

В принципе, разница между моим ответом и его ответом заключается в том, что он закрывает потоки через Semaphore, и я просто записываю результат самой быстрой нити через AtomicReference. Обратите внимание, что в моем коде я делаю что-то немного странное. А именно, я использую экземпляр AtomicReference<Integer> вместо простого AtomicInteger. Я делаю это так, чтобы я мог сравнивать и устанавливать значение в нулевое целое; Я не могу использовать нулевые целые числа с AtomicInteger. Это позволяет мне установить любое целое число, а не только набор целых чисел, за исключением некоторого значения дозорного. Кроме того, существует несколько менее важных деталей, таких как использование 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(); 
    } 
} 
Смежные вопросы