Как дождаться завершения ряда потоков?


109

Как можно просто дождаться завершения всех потоковых процессов? Например, допустим, у меня есть:

public class DoSomethingInAThread implements Runnable{

    public static void main(String[] args) {
        for (int n=0; n<1000; n++) {
            Thread t = new Thread(new DoSomethingInAThread());
            t.start();
        }
        // wait for all threads' run() methods to complete before continuing
    }

    public void run() {
        // do something here
    }


}

Как мне изменить это так, чтобы main()метод останавливался на комментарии, пока все run()методы потоков не завершились? Спасибо!

Ответы:


163

Вы помещаете все потоки в массив, запускаете их все, а затем получаете цикл

for(i = 0; i < threads.length; i++)
  threads[i].join();

Каждое соединение будет блокироваться до завершения соответствующего потока. Потоки могут завершаться в другом порядке, чем вы к ним присоединяетесь, но это не проблема: когда цикл завершается, все потоки завершаются.


1
@Mykola: в чем именно преимущество использования группы потоков? Тот факт, что API существует, не означает, что вы должны его использовать ...
Мартин против Лёвиса,

2
См .: «Группа потоков представляет собой набор потоков». Это семантически правильно для данного варианта использования! И: «Потоку разрешен доступ к информации о своей собственной группе потоков»
Мартин К.

4
В книге «Эффективная Java» рекомендуется избегать групп потоков (пункт 73).
Бастьен Леонар,

2
Ошибки, упомянутые в Эффективной Java, должны были быть исправлены в Java 6. Если новые версии Java не являются ограничением, лучше использовать Futures для решения проблем потоков. Мартин фон Лёвис: Вы правы. Это несущественно для этой проблемы, но приятно получить больше информации о запущенных потоках из одного объекта (например, ExecutorService). Я считаю, что использовать данные функции для решения проблемы - это хорошо; возможно, вам понадобится больше гибкости (информация о потоках) в будущем. Также правильно упомянуть старые классы с ошибками в старых JDK.
Мартин К.

5
ThreadGroup не реализует соединение на уровне группы, поэтому, почему люди нажимают ThreadGroup, немного сбивает с толку. Действительно ли люди используют спин-блокировки и запрашивают activeCount группы? Вам будет сложно убедить меня в том, что это лучше в любом случае по сравнению с простым вызовом join для всех потоков.

41

Один из способов - создать a Listиз Threads, создать и запустить каждый поток, добавляя его в список. Как только все будет запущено, вернитесь к списку и вызовитеjoin() каждый из них. Не имеет значения, в каком порядке завершаются выполнение потоков, все, что вам нужно знать, это то, что к тому времени, когда завершится выполнение второго цикла, все потоки будут завершены.

Лучшим подходом является использование ExecutorService и связанных с ним методов:

List<Callable> callables = ... // assemble list of Callables here
                               // Like Runnable but can return a value
ExecutorService execSvc = Executors.newCachedThreadPool();
List<Future<?>> results = execSvc.invokeAll(callables);
// Note: You may not care about the return values, in which case don't
//       bother saving them

Использование ExecutorService (и всех новых вещей из утилит параллелизма Java 5 ) невероятно гибко, и приведенный выше пример едва ли затрагивает поверхность.


ThreadGroup - это то, что вам нужно! С изменяемым списком у вас будут проблемы (синхронизация)
Мартин К.

3
Какой? Как бы вы попали в беду? Это только изменяемый (только читаемый) поток, который выполняет запуск, так что пока он не изменяет список во время итерации по нему, все в порядке.
Адам Баткин

Это зависит от того, как вы его используете. Если вы будете использовать вызывающий класс в потоке, у вас возникнут проблемы.
Мартин К.

27
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class DoSomethingInAThread implements Runnable
{
   public static void main(String[] args) throws ExecutionException, InterruptedException
   {
      //limit the number of actual threads
      int poolSize = 10;
      ExecutorService service = Executors.newFixedThreadPool(poolSize);
      List<Future<Runnable>> futures = new ArrayList<Future<Runnable>>();

      for (int n = 0; n < 1000; n++)
      {
         Future f = service.submit(new DoSomethingInAThread());
         futures.add(f);
      }

      // wait for all tasks to complete before continuing
      for (Future<Runnable> f : futures)
      {
         f.get();
      }

      //shut down the executor service so that this thread can exit
      service.shutdownNow();
   }

   public void run()
   {
      // do something here
   }
}

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

@Dantalian - в вашем классе Runnable (вероятно, в методе run) вы захотите зафиксировать любые возникшие исключения и сохранить их локально (или сохранить сообщение об ошибке / условие). В этом примере f.get () возвращает ваш объект, который вы отправили в ExecutorService. У вашего объекта может быть метод для получения каких-либо исключений / ошибок. В зависимости от того, как вы изменяете предоставленный пример, вам может потребоваться привести объект, превращенный f.get (), к ожидаемому типу.
jt.

12

вместо join()старого API можно использовать CountDownLatch . Я изменил ваш код, как показано ниже, чтобы выполнить ваше требование.

import java.util.concurrent.*;
class DoSomethingInAThread implements Runnable{
    CountDownLatch latch;
    public DoSomethingInAThread(CountDownLatch latch){
        this.latch = latch;
    } 
    public void run() {
        try{
            System.out.println("Do some thing");
            latch.countDown();
        }catch(Exception err){
            err.printStackTrace();
        }
    }
}

public class CountDownLatchDemo {
    public static void main(String[] args) {
        try{
            CountDownLatch latch = new CountDownLatch(1000);
            for (int n=0; n<1000; n++) {
                Thread t = new Thread(new DoSomethingInAThread(latch));
                t.start();
            }
            latch.await();
            System.out.println("In Main thread after completion of 1000 threads");
        }catch(Exception err){
            err.printStackTrace();
        }
    }
}

Пояснение :

  1. CountDownLatch был инициализирован с заданным счетчиком 1000 согласно вашему требованию.

  2. Каждый рабочий поток DoSomethingInAThread будет уменьшать значение CountDownLatch, переданное в конструктор.

  3. Основной поток, CountDownLatchDemo await()пока счетчик не станет равным нулю. Как только счетчик станет нулевым, вы увидите строку ниже на выходе.

    In Main thread after completion of 1000 threads

Дополнительная информация на странице документации Oracle

public void await()
           throws InterruptedException

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

Обратитесь к соответствующему вопросу SE для других вариантов:

подождите, пока все потоки закончат свою работу в java


8

Полностью избегайте класса Thread и вместо этого используйте более высокие абстракции, представленные в java.util.concurrent

Класс ExecutorService предоставляет метод invokeAll, который, кажется, делает именно то, что вы хотите.


6

Рассмотрите возможность использования java.util.concurrent.CountDownLatch. Примеры в javadocs


Защелка для резьбы, замок-защелка работает с обратным отсчетом. В методе run () вашего потока явно объявите, что нужно ждать, пока CountDownLatch достигнет обратного отсчета до 0. Вы можете использовать один и тот же CountDownLatch в более чем одном потоке, чтобы освободить их одновременно. Не знаю, нужно ли это вам, просто хотел упомянуть, потому что это полезно при работе в многопоточной среде.
Пабло Кавальери

Может быть, вам стоит поместить это объяснение в текст своего ответа?
Аарон Холл

Примеры в Javadoc очень наглядны, поэтому я не добавил их. docs.oracle.com/javase/7/docs/api/java/util/concurrent/… . В первом примере все потоки Workers выпускаются одновременно, потому что они ждут, пока CountdownLatch startSignal достигнет нуля, что происходит в startSignal.countDown (). Затем поток mian ожидает завершения всех работ, используя инструкцию doneSignal.await (). doneSignal уменьшает его значение для каждого рабочего.
Пабло Кавальери

6

Как предложил Мартин К., это java.util.concurrent.CountDownLatchкажется лучшим решением для этого. Просто добавляю пример того же

     public class CountDownLatchDemo
{

    public static void main (String[] args)
    {
        int noOfThreads = 5;
        // Declare the count down latch based on the number of threads you need
        // to wait on
        final CountDownLatch executionCompleted = new CountDownLatch(noOfThreads);
        for (int i = 0; i < noOfThreads; i++)
        {
            new Thread()
            {

                @Override
                public void run ()
                {

                    System.out.println("I am executed by :" + Thread.currentThread().getName());
                    try
                    {
                        // Dummy sleep
                        Thread.sleep(3000);
                        // One thread has completed its job
                        executionCompleted.countDown();
                    }
                    catch (InterruptedException e)
                    {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }

            }.start();
        }

        try
        {
            // Wait till the count down latch opens.In the given case till five
            // times countDown method is invoked
            executionCompleted.await();
            System.out.println("All over");
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }

}

4

В зависимости от ваших потребностей, вы также можете проверить классы CountDownLatch и CyclicBarrier в пакете java.util.concurrent. Они могут быть полезны, если вы хотите, чтобы ваши потоки ожидали друг друга, или если вы хотите более детальный контроль над тем, как ваши потоки выполняются (например, ожидая в своем внутреннем исполнении, чтобы другой поток установил какое-то состояние). Вы также можете использовать CountDownLatch, чтобы сигнализировать всем вашим потокам о запуске одновременно, вместо того, чтобы запускать их один за другим, когда вы повторяете свой цикл. В стандартных документах API есть пример этого, а также использование другого CountDownLatch для ожидания завершения выполнения всеми потоками.


3

Если вы составите список потоков, вы можете перебрать их и выполнить .join () для каждого, и ваш цикл завершится, когда все потоки будут. Но я не пробовал.

http://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html#join ()


Привет, у меня почему-то не сработало. Вот мой вопрос: stackoverflow.com/users/5144855/ruchir-baronia
Ruchir Baronia

1

Создайте объект потока внутри первого цикла for.

for (int i = 0; i < threads.length; i++) {
     threads[i] = new Thread(new Runnable() {
         public void run() {
             // some code to run in parallel
         }
     });
     threads[i].start();
 }

Итак, что все здесь говорят.

for(i = 0; i < threads.length; i++)
  threads[i].join();

0

Вы можете сделать это с помощью объекта ThreadGroup и его параметра activeCount :


Не знаю, как именно вы предлагаете это сделать. Если вы предлагаете опросить activeCount в цикле: это плохо, так как он занят-подождите (даже если вы спите между опросами - тогда вы получите компромисс между бизнесом и скоростью реагирования).
Мартин против Лёвиса,

@Martin v. Löwis: «Присоединение будет ждать только одного потока. Лучшим решением может быть java.util.concurrent.CountDownLatch. Просто инициализируйте защелку, установив счетчик на количество рабочих потоков. Каждый рабочий поток должен вызывать countDown () непосредственно перед своим выходом, а основной поток просто вызывает await (), который будет блокироваться, пока счетчик не достигнет нуля. Проблема с join () также заключается в том, что вы не можете начать динамическое добавление дополнительных потоков. Список будет расширяться с одновременной модификацией ". Ваше решение отлично подходит для проблемы, но не для общих целей.
Мартин К.

0

В качестве альтернативы CountDownLatch вы также можете использовать CyclicBarrier, например

public class ThreadWaitEx {
    static CyclicBarrier barrier = new CyclicBarrier(100, new Runnable(){
        public void run(){
            System.out.println("clean up job after all tasks are done.");
        }
    });
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            Thread t = new Thread(new MyCallable(barrier));
            t.start();
        }       
    }

}    

class MyCallable implements Runnable{
    private CyclicBarrier b = null;
    public MyCallable(CyclicBarrier b){
        this.b = b;
    }
    @Override
    public void run(){
        try {
            //do something
            System.out.println(Thread.currentThread().getName()+" is waiting for barrier after completing his job.");
            b.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }       
}

Чтобы использовать CyclicBarrier в этом случае, в последнем операторе должен быть барьер .await (), т.е. когда ваш поток завершил свою работу. CyclicBarrier можно снова использовать с его методом reset (). Чтобы процитировать javadocs:

CyclicBarrier поддерживает необязательную команду Runnable, которая запускается один раз для каждой точки барьера после прибытия последнего потока в группе, но до того, как какие-либо потоки будут освобождены. Это действие барьера полезно для обновления общего состояния до продолжения какой-либо из сторон.


Я не думаю, что это хороший пример CyclicBarrier. Почему вы используете вызов Thread.sleep ()?
Гюнтер

@Guenther - да, я изменил код, чтобы удовлетворить требованиям.
shailendra1118

CyclicBarrier не является альтернативой CountDownLatch. Когда потоки должны постоянно вести обратный отсчет, вы должны создать CyclicBarrier, в противном случае по умолчанию используется CountDownLatch (если иное не требуется дополнительной абстракции Execution, после чего вам следует обратиться к более высокоуровневым службам).
Elysiumplain

0

join()Не было полезно для меня. см. этот образец в Котлине:

    val timeInMillis = System.currentTimeMillis()
    ThreadUtils.startNewThread(Runnable {
        for (i in 1..5) {
            val t = Thread(Runnable {
                Thread.sleep(50)
                var a = i
                kotlin.io.println(Thread.currentThread().name + "|" + "a=$a")
                Thread.sleep(200)
                for (j in 1..5) {
                    a *= j
                    Thread.sleep(100)
                    kotlin.io.println(Thread.currentThread().name + "|" + "$a*$j=$a")
                }
                kotlin.io.println(Thread.currentThread().name + "|TaskDurationInMillis = " + (System.currentTimeMillis() - timeInMillis))
            })
            t.start()
        }
    })

Результат:

Thread-5|a=5
Thread-1|a=1
Thread-3|a=3
Thread-2|a=2
Thread-4|a=4
Thread-2|2*1=2
Thread-3|3*1=3
Thread-1|1*1=1
Thread-5|5*1=5
Thread-4|4*1=4
Thread-1|2*2=2
Thread-5|10*2=10
Thread-3|6*2=6
Thread-4|8*2=8
Thread-2|4*2=4
Thread-3|18*3=18
Thread-1|6*3=6
Thread-5|30*3=30
Thread-2|12*3=12
Thread-4|24*3=24
Thread-4|96*4=96
Thread-2|48*4=48
Thread-5|120*4=120
Thread-1|24*4=24
Thread-3|72*4=72
Thread-5|600*5=600
Thread-4|480*5=480
Thread-3|360*5=360
Thread-1|120*5=120
Thread-2|240*5=240
Thread-1|TaskDurationInMillis = 765
Thread-3|TaskDurationInMillis = 765
Thread-4|TaskDurationInMillis = 765
Thread-5|TaskDurationInMillis = 765
Thread-2|TaskDurationInMillis = 765

Теперь позвольте мне использовать join()для потоков:

    val timeInMillis = System.currentTimeMillis()
    ThreadUtils.startNewThread(Runnable {
        for (i in 1..5) {
            val t = Thread(Runnable {
                Thread.sleep(50)
                var a = i
                kotlin.io.println(Thread.currentThread().name + "|" + "a=$a")
                Thread.sleep(200)
                for (j in 1..5) {
                    a *= j
                    Thread.sleep(100)
                    kotlin.io.println(Thread.currentThread().name + "|" + "$a*$j=$a")
                }
                kotlin.io.println(Thread.currentThread().name + "|TaskDurationInMillis = " + (System.currentTimeMillis() - timeInMillis))
            })
            t.start()
            t.join()
        }
    })

И результат:

Thread-1|a=1
Thread-1|1*1=1
Thread-1|2*2=2
Thread-1|6*3=6
Thread-1|24*4=24
Thread-1|120*5=120
Thread-1|TaskDurationInMillis = 815
Thread-2|a=2
Thread-2|2*1=2
Thread-2|4*2=4
Thread-2|12*3=12
Thread-2|48*4=48
Thread-2|240*5=240
Thread-2|TaskDurationInMillis = 1568
Thread-3|a=3
Thread-3|3*1=3
Thread-3|6*2=6
Thread-3|18*3=18
Thread-3|72*4=72
Thread-3|360*5=360
Thread-3|TaskDurationInMillis = 2323
Thread-4|a=4
Thread-4|4*1=4
Thread-4|8*2=8
Thread-4|24*3=24
Thread-4|96*4=96
Thread-4|480*5=480
Thread-4|TaskDurationInMillis = 3078
Thread-5|a=5
Thread-5|5*1=5
Thread-5|10*2=10
Thread-5|30*3=30
Thread-5|120*4=120
Thread-5|600*5=600
Thread-5|TaskDurationInMillis = 3833

Это ясно, когда мы используем join:

  1. Потоки выполняются последовательно.
  2. Первая выборка занимает 765 миллисекунд, а вторая - 3833 миллисекунды.

Нашим решением для предотвращения блокировки других потоков было создание ArrayList:

val threads = ArrayList<Thread>()

Теперь, когда мы хотим запустить новый поток, мы чаще всего добавляем его в ArrayList:

addThreadToArray(
    ThreadUtils.startNewThread(Runnable {
        ...
    })
)

addThreadToArrayФункция:

@Synchronized
fun addThreadToArray(th: Thread) {
    threads.add(th)
}

startNewThreadFunstion:

fun startNewThread(runnable: Runnable) : Thread {
    val th = Thread(runnable)
    th.isDaemon = false
    th.priority = Thread.MAX_PRIORITY
    th.start()
    return th
}

Проверяйте завершение потоков, как показано ниже, везде, где это необходимо:

val notAliveThreads = ArrayList<Thread>()
for (t in threads)
    if (!t.isAlive)
        notAliveThreads.add(t)
threads.removeAll(notAliveThreads)
if (threads.size == 0){
    // The size is 0 -> there is no alive threads.
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.