Як дочекатися завершення кількості ниток?


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 є, це не означає, що вам доведеться його використовувати ...
Martin v. Löwis

2
Див. "Група ниток являє собою набір потоків." Це семантично правильно для цього випадку використання! І: "Нитці дозволено отримувати доступ до інформації про власну групу потоків"
Мартін К.

4
Книга "Ефективна Java" рекомендує уникати груп потоків (пункт 73).
Бастієн Леонард

2
Помилки, згадані в «Ефективній Java», повинні були бути виправлені в Java 6. Якщо новіші версії Java не є обмеженням, краще використовувати Futures для вирішення проблем ниток. Мартін проти Левіса: Ти маєш рацію. Ця проблема не потрібна, але приємно отримати більше інформації про запущені потоки з одного Об'єкта (наприклад, у ExecutorService). Я думаю, що приємно використовувати задані функції для вирішення проблеми; можливо, в майбутньому вам буде потрібна більша гнучкість (інформація про нитки). Також правильно згадати старі заняття баггі в старих JDK.
Мартін К.

5
ThreadGroup не реалізує приєднання на рівні групи, тому чому люди підштовхують ThreadGroup - це трохи неприємно. Чи справді люди використовують спінові блокування та запитують ActiveCount групи? Мені буде важко переконати мене, що робити це краще будь-яким чином порівняно з просто дзвінками приєднатись до всіх потоків.

41

Одним з способів було б зробити Listз Threadс, створювати і запускати кожен потік, при додаванні його до списку. Після того, як все запущено, проведіть назад список і зателефонуйте 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 (ймовірно, у методі запуску) ви хочете зафіксувати будь-які винятки, що траплялися, та зберігати їх локально (або зберігати повідомлення про помилку / стан). У прикладі 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 - це випуски одночасно, оскільки вони чекають, коли startSignal countdownLatch досягне нуля, що відбувається в startSignal.countDown (). Потім міан-нитка чекає, поки всі роботи закінчать, використовуючи інструкцію 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 (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 у цьому випадку barrier.await () має бути останньою заявою, тобто коли ваша нитка виконана зі своєю роботою. CyclicBarrier можна знову використовувати методом його скидання (). Щоб цитувати javadocs:

CyclicBarrier підтримує необов'язкову команду Runnable, яка виконується один раз за бар'єрною точкою, після того, як надходить останній потік у партії, але до виходу будь-яких потоків. Ця бар'єрна дія корисна для оновлення загального стану, перш ніж будь-яка із сторін продовжить.


Я не думаю, що це хороший приклад для CyclicBarrier. Чому ви використовуєте виклик Thread.sleep ()?
Гюнтер

@Guenther - так, я змінив код відповідно до вимоги.
shailendra1118

CyclicBarrier не є альтернативою CountDownLatch. Коли нитки повинні неодноразово відраховуватись, вам слід створити CyclicBarrier, інакше за замовчуванням CountDownLatch (якщо інше не вимагає додаткової абстракції виконання, після чого слід звернутися до вищого рівня, Служби).
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.