зачекайте, поки всі потоки закінчать свою роботу в Java


91

Я пишу додаток, яке містить 5 потоків, які одночасно отримують деяку інформацію з Інтернету та заповнюють 5 різних полів у буферному класі.
Мені потрібно перевірити дані буфера та зберегти їх у базі даних, коли всі потоки закінчать свою роботу.
Як я можу це зробити (отримувати сповіщення, коли всі потоки закінчать свою роботу)?


4
Thread.join - це досить низькорівневий дуже ідіосинхранічний спосіб Java для вирішення проблеми. Крім того , це проблематично , так як Thread API зіпсований , ви не можете знати , є чи приєднатися завершена успішно чи ні (див Java Concurrency на практиці ). Абстракція вищого рівня, така як використання CountDownLatch, може бути кращою і виглядатиме більш природно для програмістів, які не "застрягли" в ідіосинхратичному мисленні Java. Не сперечайтеся зі мною, ідіть сперечатися з Дагом Лією; )
Седрік Мартін,

Відповіді:


119

Я використовую підхід до використання ExecutorService для управління пулами потоків.

ExecutorService es = Executors.newCachedThreadPool();
for(int i=0;i<5;i++)
    es.execute(new Runnable() { /*  your task */ });
es.shutdown();
boolean finished = es.awaitTermination(1, TimeUnit.MINUTES);
// all tasks have finished or the time has been reached.

7
@Leonid, це саме те, що робить shutdown ().
Пітер Лорі

3
while(!es.awaitTermination(1, TimeUnit.MINUTES));
Сила Водолія

3
@AquariusPower Ви можете просто сказати йому чекати довше або вічно.
Пітер Лорі

1
о .. розумію; тому я додав повідомлення у циклі, в якому сказано, що він чекає закінчення всіх потоків; Дякую!
Сила Водолія

1
@PeterLawrey, чи потрібно телефонувати es.shutdown();? що, якщо я напишу код, в якому я виконав потік, використовуючи es.execute(runnableObj_ZipMaking);в tryблоці і в finallyя зателефонував boolean finshed = es.awaitTermination(10, TimeUnit.MINUTES);. Тож я гадаю, що це повинно зачекати, поки всі потоки завершать свою роботу або не закінчиться час очікування (що б не було першим). Чи моє припущення правильне? або заклик до shutdown()є обов’язковим?
Амог

52

Ти можеш join до ниток. Блоки об’єднання, поки потік не завершиться.

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

Зверніть увагу, що joinкидає InterruptedException. Вам доведеться вирішити, що робити, якщо це трапиться (наприклад, спробуйте скасувати інші потоки, щоб запобігти непотрібній роботі).


1
Ці потоки працюють паралельно або послідовно один одному?
Джеймс Вебстер,

5
@JamesWebster: Паралельно.
RYN

4
@James Webster: Заява t.join();означає, що поточний потік блокується, поки потік не tзакінчиться. Це не впливає на нитку t.
Mark Byers

1
Дякую. =] Вивчав паралелізм в університеті, але це було єдине, чому я намагався навчитися! На щастя, мені зараз не потрібно його багато використовувати, або коли я це роблю, це не надто складно або немає спільних ресурсів, і блокування не є критичним
James Webster

1
@ 4r1y4n Чи дійсно наданий код дійсно паралельний, залежить від того, що ви намагаєтеся з ним робити, і більше пов’язаний з агрегуванням даних, розподілених по колекціях за допомогою об’єднаних потоків. Ви приєднуєтесь до потоків, що потенційно означає "приєднання" даних. Крім того, паралелізм НЕ обов’язково означає одночасність. Це залежить від процесорів. Цілком може бути, що потоки працюють паралельно, але обчислення відбуваються в будь-якому порядку, який визначається базовим процесором.

22

Погляньте на різні рішення.

  1. join()API був представлений у ранніх версіях Java. Деякі хороші альтернативи доступні з цим одночасним пакетом з моменту випуску JDK 1.5.

  2. ExecutorService # invokeAll ()

    Виконує задані завдання, повертаючи список ф’ючерсів, що мають їхній статус та результати, коли все завершено.

    Зверніться до цього відповідного запитання про SE для прикладу коду:

    Як використовувати invokeAll (), щоб дозволити усьому пулу потоків виконувати своє завдання?

  3. CountDownLatch

    Допомога синхронізації, яка дозволяє одному або декільком потокам чекати, поки завершиться набір операцій, що виконуються в інших потоках.

    CountDownLatch инициализируется з заданим кол. Методи await блокуються до тих пір, поки поточний рахунок не досягне нуля через виклики countDown()методу, після чого всі потоки очікування звільняються, а всі наступні виклики await негайно повертаються. Це одноразове явище - підрахунок неможливо скинути. Якщо вам потрібна версія, яка скидає підрахунок, розгляньте можливість використання CyclicBarrier .

    Зверніться до цього питання щодо використання CountDownLatch

    Як чекати, поки нитка породить власну нитку?

  4. ForkJoinPool або newWorkStealingPool () у виконавцях

  5. Переглядайте всі майбутні об’єкти, створені після поданняExecutorService


11

Окрім Thread.join()запропонованих іншими, Java 5 представив рамки виконавця. Там ви не працюєте з Threadоб’єктами. Натомість ви подаєте свої Callableабо Runnableпредмети виконавцю. Існує спеціальний виконавець, який призначений для виконання кількох завдань і повернення їх результатів із порядку. Це ExecutorCompletionService:

ExecutorCompletionService executor;
for (..) {
    executor.submit(Executors.callable(yourRunnable));
}

Потім ви можете повторно телефонувати, take()поки не буде більше Future<?>об’єктів для повернення, а це означає, що всі вони завершені.


Інша річ, яка може бути актуальною, залежно від вашого сценарію CyclicBarrier.

Допомога синхронізації, яка дозволяє набору потоків всім очікувати, поки один одного дійде до загальної бар’єрної точки. CyclicBarriers корисні в програмах, що включають партію потоків фіксованого розміру, які час від часу повинні чекати один одного. Бар'єр називається циклічним, оскільки його можна використовувати повторно після випуску потоків очікування.


Це близько, але я все одно вніс би кілька коригувань. executor.submitповертає a Future<?>. Я б додав ці ф'ючерси до списку, а потім прокрутив список, закликаючи getкожного майбутнього.
Рей

Крім того, ви можете створити екземпляр конструктора, використовуючи Executors, наприклад, Executors.newCachedThreadPool(або подібний)
Рей

10

Іншою можливістю є CountDownLatchоб'єкт, який корисний для простих ситуацій: оскільки ви заздалегідь знаєте кількість потоків, ви ініціалізуєте його відповідним рахунком і передаєте посилання на об'єкт кожному потоку.
Після завершення свого завдання кожен потік викликає, CountDownLatch.countDown()що зменшує внутрішній лічильник. Після запуску всіх інших основний потік повинен зробити CountDownLatch.await()виклик блокування. Він буде випущений, як тільки внутрішній лічильник досягне 0.

Зверніть увагу, що з цим предметом InterruptedExceptionможна також кинути і кузов.


8

Ти робиш

for (Thread t : new Thread[] { th1, th2, th3, th4, th5 })
    t.join()

Після цього циклу for ви можете бути впевнені, що всі потоки закінчили свої завдання.


6

Зачекайте / заблокуйте Thread Main, поки деякі інші потоки не завершать свою роботу.

Як вже було @Ravindra babuсказано, цього можна досягти різними способами, але показавши на прикладах.

  • java.lang.Thread. join () Оскільки: 1.0

    public static void joiningThreads() throws InterruptedException {
        Thread t1 = new Thread( new LatchTask(1, null), "T1" );
        Thread t2 = new Thread( new LatchTask(7, null), "T2" );
        Thread t3 = new Thread( new LatchTask(5, null), "T3" );
        Thread t4 = new Thread( new LatchTask(2, null), "T4" );
    
        // Start all the threads
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    
        // Wait till all threads completes
        t1.join();
        t2.join();
        t3.join();
        t4.join();
    }
  • java.util.concurrent.CountDownLatch З: 1.5

    • .countDown() «Зменшує кількість груп засувок.
    • .await() «Методи очікування блокуються, поки поточний рахунок не досягне нуля.

    Якщо ви створили, latchGroupCount = 4тоді вам countDown()слід викликати 4 рази, щоб зробити відлік 0. Отже, це await()звільнить блокуючі потоки.

    public static void latchThreads() throws InterruptedException {
        int latchGroupCount = 4;
        CountDownLatch latch = new CountDownLatch(latchGroupCount);
        Thread t1 = new Thread( new LatchTask(1, latch), "T1" );
        Thread t2 = new Thread( new LatchTask(7, latch), "T2" );
        Thread t3 = new Thread( new LatchTask(5, latch), "T3" );
        Thread t4 = new Thread( new LatchTask(2, latch), "T4" );
    
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    
        //latch.countDown();
    
        latch.await(); // block until latchGroupCount is 0.
    }

Приклад коду потокового класу LatchTask. Для тестування підходу використовують joiningThreads(); і latchThreads();основний метод.

class LatchTask extends Thread {
    CountDownLatch latch;
    int iterations = 10;
    public LatchTask(int iterations, CountDownLatch latch) {
        this.iterations = iterations;
        this.latch = latch;
    }

    @Override
    public void run() {
        String threadName = Thread.currentThread().getName();
        System.out.println(threadName + " : Started Task...");

        for (int i = 0; i < iterations; i++) {
            System.out.println(threadName + " : " + i);
            MainThread_Wait_TillWorkerThreadsComplete.sleep(1);
        }
        System.out.println(threadName + " : Completed Task");
        // countDown() « Decrements the count of the latch group.
        if(latch != null)
            latch.countDown();
    }
}
  • CyclicBarriers - допоміжний пристрій для синхронізації, що дозволяє набору потоків всім очікувати, поки один одного дійде до загальної точки бар'єру. Бар'єр називається циклічним, оскільки його можна повторно використовувати після звільнення потоків очікування.
    CyclicBarrier barrier = new CyclicBarrier(3);
    barrier.await();
    Наприклад, зверніться до цього класу Concurrent_ParallelNotifyies .

  • Фреймворк виконавця: ми можемо використовувати ExecutorService для створення пулу потоків і відстежуємо хід асинхронних завдань за допомогою Future.

    • submit(Runnable), submit(Callable)які повертають Майбутній об'єкт. За допомогою future.get()функції ми можемо блокувати основний потік, поки робочі потоки не завершать свою роботу.

    • invokeAll(...) - повертає список майбутніх об'єктів, за допомогою яких ви можете отримати результати виконання кожного Callable.

Знайдіть приклад використання інтерфейсів Runnable, Callable with Executor framework.


@Дивитися також


4

Зберігайте Thread-об'єкти в деякій колекції (наприклад, у списку або наборі), а потім прокручуйте колекцію після запуску потоків і викликайте join () у Threads.



2

Хоча це не стосується проблеми OP, якщо ви зацікавлені в синхронізації (точніше, rendez-vous) із точно одним потоком, ви можете використовувати Exchanger

У моєму випадку мені потрібно було призупинити батьківський потік, поки дочірній потік щось не зробив, наприклад, не завершив його ініціалізацію. CountDownLatch також працює добре.



1

спробуйте це, буде працювати.

  Thread[] threads = new Thread[10];

  List<Thread> allThreads = new ArrayList<Thread>();

  for(Thread thread : threads){

        if(null != thread){

              if(thread.isAlive()){

                    allThreads.add(thread);

              }

        }

  }

  while(!allThreads.isEmpty()){

        Iterator<Thread> ite = allThreads.iterator();

        while(ite.hasNext()){

              Thread thread = ite.next();

              if(!thread.isAlive()){

                   ite.remove();
              }

        }

   }

1

У мене була подібна проблема, і я закінчив користуватися Java 8 паралельним потоком.

requestList.parallelStream().forEach(req -> makeRequest(req));

Це надзвичайно просто та читабельно. За лаштунками він використовує пул приєднання форків JVM за замовчуванням, що означає, що він буде чекати закінчення всіх потоків, перш ніж продовжувати. Для мого випадку це було акуратне рішення, оскільки це був єдиний паралельний потік у моїй програмі. Якщо у вас одночасно працює кілька паралельних потоків, прочитайте посилання нижче.

Більше інформації про паралельні потоки тут .


0

Існуючі відповіді сказали, що може join()кожна нитка.

Але є кілька способів отримати масив / список потоків:

  • Додайте нитку до списку при створенні.
  • Використовуйте ThreadGroupдля управління потоками.

Наступний код буде використовувати ThreadGruop підхід. Він створює групу спочатку, потім при створенні кожного потоку вказує групу в конструкторі, пізніше може отримати масив потоку черезThreadGroup.enumerate()


Код

SyncBlockLearn.java

import org.testng.Assert;
import org.testng.annotations.Test;

/**
 * synchronized block - learn,
 *
 * @author eric
 * @date Apr 20, 2015 1:37:11 PM
 */
public class SyncBlockLearn {
    private static final int TD_COUNT = 5; // thread count
    private static final int ROUND_PER_THREAD = 100; // round for each thread,
    private static final long INC_DELAY = 10; // delay of each increase,

    // sync block test,
    @Test
    public void syncBlockTest() throws InterruptedException {
        Counter ct = new Counter();
        ThreadGroup tg = new ThreadGroup("runner");

        for (int i = 0; i < TD_COUNT; i++) {
            new Thread(tg, ct, "t-" + i).start();
        }

        Thread[] tArr = new Thread[TD_COUNT];
        tg.enumerate(tArr); // get threads,

        // wait all runner to finish,
        for (Thread t : tArr) {
            t.join();
        }

        System.out.printf("\nfinal count: %d\n", ct.getCount());
        Assert.assertEquals(ct.getCount(), TD_COUNT * ROUND_PER_THREAD);
    }

    static class Counter implements Runnable {
        private final Object lkOn = new Object(); // the object to lock on,
        private int count = 0;

        @Override
        public void run() {
            System.out.printf("[%s] begin\n", Thread.currentThread().getName());

            for (int i = 0; i < ROUND_PER_THREAD; i++) {
                synchronized (lkOn) {
                    System.out.printf("[%s] [%d] inc to: %d\n", Thread.currentThread().getName(), i, ++count);
                }
                try {
                    Thread.sleep(INC_DELAY); // wait a while,
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            System.out.printf("[%s] end\n", Thread.currentThread().getName());
        }

        public int getCount() {
            return count;
        }
    }
}

Основний потік буде чекати закінчення всіх потоків у групі.


0

Я створив невеликий допоміжний метод, щоб зачекати, поки закінчиться кілька ниток:

public static void waitForThreadsToFinish(Thread... threads) {
        try {
            for (Thread thread : threads) {
                thread.join();
            }
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

-1

Використовуйте це у своїй головній темі: while (! Executor.isTerminated ()); Помістіть цей рядок коду після запуску всіх потоків із служби виконавця. Це почне основний потік лише після того, як будуть завершені всі потоки, розпочаті виконавцями. Обов’язково зателефонуйте виконавцю.shutdown (); перед вищезазначеною петлею.


Це активне очікування, що призведе до постійного запуску процесора порожнього циклу. Дуже марнотратно.
Адам Міхалік
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.