Этот ответ основан на ответе @ 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();
}
}
Есть ли причина, чтобы не использовать [ExecutorService.invokeAny] (https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html#invokeAny-java. util.Collection-)? – VGR
@ VGR Я просто не знал, что этот метод существует, но именно поэтому я написал этот вопрос. Я просто попробовал новый код с помощью «ExecutorService.invokeAny()», и он значительно упрощает код. Если вы добавите ответ, указывающий на это, я буду рад проголосовать за него и принять его. Огромное спасибо! – entpnerd