Ожидание выполнения нескольких потоков в Java

Во время выполнения моей программы запускается несколько потоков. Количество потоков зависит от определенных пользователем параметров, но все они выполняют один и тот же метод с разными переменными.

В некоторых ситуациях для очистки требуется среднее выполнение, часть этого останавливает все потоки, я не хочу, чтобы они останавливались немедленно, но я просто установил переменную, которую они проверяют, для того, чтобы это их прекратило. Проблема в том, что она может занять до 1/2 секунды до остановки потока. Тем не менее, я должен быть уверен, что все потоки остановились, прежде чем очистка может продолжаться. Очистка выполняется из другого потока, так что технически мне нужен этот поток, чтобы дождаться окончания остальных потоков.

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

Спасибо.

Ответ 1

Просто присоединитесь к ним один за другим:

for (Thread thread : threads) {
  thread.join();
}

(Вам нужно что-то сделать с InterruptedException, и вы вполне можете захотеть предоставить тайм-аут на случай, если все пойдет не так, но что основная идея...)

Ответ 2

Если вы используете java 1.5 или выше, вы можете попробовать CyclicBarrier. Вы можете передать операцию очистки как свой параметр конструктора и просто вызвать barrier.await() для всех потоков, когда есть необходимость в очистке.

Ответ 3

Определите способ утилиты (или методы) самостоятельно:

public static waitFor(Collection<? extends Thread) c) throws InterruptedException {
    for(Thread t : c) t.join();
}

Или у вас может быть массив

public static waitFor(Thread[] ts) throws InterruptedException {
    waitFor(Arrays.asList(ts));
}

В качестве альтернативы вы можете использовать CyclicBarrier в библиотеке java.util.concurrent для реализации произвольной точки рандеву между несколькими потоками.

Ответ 4

Вы видели классы Executor в java.util.concurrent? Вы можете запускать свои потоки через ExecutorService. Он дает вам один объект, который вы можете использовать для отмены потоков или ждать их завершения.

Ответ 5

Если вы контролируете создание потоков (представление в ExecutorService), то, похоже, вы можете использовать ExecutorCompletionService см. ExecutorCompletionService? Зачем нужен один, если у нас есть invokeAll? для различных ответов там.

Если вы не контролируете создание потоков, вот подход, который позволяет вам присоединяться к потокам "один за другим по мере их завершения" (и знать, какой из них заканчивается первым и т.д.), вдохновленный рубином ThreadWait. В основном, обновляя "просмотр потоков", которые предупреждают о завершении других потоков, вы можете знать, когда "следующий" поток из многих завершается.

Вы бы использовали его примерно так:

JoinThreads join = new JoinThreads(threads);
for(int i = 0; i < threads.size(); i++) {
  Thread justJoined = join.joinNextThread();
  System.out.println("Done with a thread, just joined=" + justJoined);
}

И источник:

public static class JoinThreads {
  java.util.concurrent.LinkedBlockingQueue<Thread> doneThreads = 
      new LinkedBlockingQueue<Thread>();

  public JoinThreads(List<Thread> threads) {
    for(Thread t : threads) {
      final Thread joinThis = t;
      new Thread(new Runnable() {
        @Override
        public void run() {
          try {
            joinThis.join();
            doneThreads.add(joinThis);
          }
          catch (InterruptedException e) {
            // "should" never get here, since we control this thread and don't call interrupt on it
          }
        }
      }).start();
    }

  }

  Thread joinNextThread() throws InterruptedException {
    return doneThreads.take();
  }
}

Хорошей частью этого является то, что он работает с генерическими потоками Java, без изменений, любой поток может быть соединен. Предостережение это требует некоторого дополнительного создания потока. Кроме того, эта конкретная реализация "оставляет нити за собой", если вы не вызываете joinNextThread() полное количество раз и не имеете метода "close" и т.д. Комментарий здесь, если вы хотите создать более полированную версию. Вы также можете использовать тот же тип шаблона с "Futures" вместо объектов Thread и т.д.