모든 스레드가 Java에서 작업을 마칠 때까지 기다립니다.


94

웹에서 동시에 정보를 가져오고 버퍼 클래스에서 5 개의 다른 필드를 채우는 5 개의 스레드가있는 애플리케이션을 작성 중입니다.
모든 스레드가 작업을 마쳤을 때 버퍼 데이터의 유효성을 검사하고 데이터베이스에 저장해야합니다.
어떻게해야합니까 (모든 스레드가 작업을 마쳤을 때 알림을 받음)?


4
Thread.join 은 문제를 해결하기 위해 다소 낮은 수준의 매우 Java 특이한 방법입니다. 게다가 Thread API에 결함이 있기 때문에 문제가 있습니다. 조인 이 성공적으로 완료 되었는지 여부를 알 수 없습니다 ( 실습에서 Java 동시성 참조 ). CountDownLatch를 사용하는 것과 같은 더 높은 수준의 추상화 가 선호 될 수 있으며 Java 고유의 ​​사고 방식에 "고착"되지 않은 프로그래머에게는 더 자연스럽게 보일 것입니다. 나와 다투지 말고 Doug Lea와 다투십시오. )
Cedric Martin

답변:


121

내가 취하는 접근 방식은 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 ()이하는 일입니다.
Peter Lawrey 2013-10-11

3
while(!es.awaitTermination(1, TimeUnit.MINUTES));
Aquarius Power

3
@AquariusPower 당신은 더 오래 또는 영원히 기다리라고 말할 수 있습니다.
Peter Lawrey 2014

1
아, 알겠습니다; 그래서 모든 스레드가 완료되기를 기다리고 있다는 메시지를 루프에 추가했습니다. 감사!
Aquarius Power

1
@PeterLawrey, 전화 es.shutdown();해야합니까? 무엇을 내가 사용하여 스레드를 실행하는 코드를 작성하는 경우 es.execute(runnableObj_ZipMaking);try블록에 finally내가 전화를 boolean finshed = es.awaitTermination(10, TimeUnit.MINUTES);. 따라서 모든 스레드가 작업을 완료하거나 시간 초과가 발생할 때까지 기다려야한다고 생각합니다 (먼저 무엇이든), 내 가정이 맞습니까? 또는 전화 shutdown()는 필수입니까?
Amogh

53

join스레드에 할 수 있습니다 . 스레드가 완료 될 때까지 결합이 차단됩니다.

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

참고 join가 발생합니다 InterruptedException. 이런 일이 발생하면 무엇을할지 결정해야합니다 (예 : 불필요한 작업이 수행되지 않도록 다른 스레드를 취소).


1
이러한 스레드가 서로 병렬로 실행됩니까 아니면 순차적으로 실행됩니까?
James Webster

5
@JamesWebster : 병렬.
RYN

4
@James Webster :이 문장 은 스레드가 종료 될 때까지 현재 스레드가 차단됨을 t.join();의미합니다 . 스레드에는 영향을주지 않습니다 . tt
Mark Byers

1
감사. =] uni에서 평행주의를 공부했지만 그게 내가 배우기 위해 고군분투 한 유일한 것입니다! 다행히 지금은 많이 사용하지 않아도 아니면 내가 너무 복잡하지 없습니다 또는 어떤 자원이 공유 및 차단이 중요하지 할 때
제임스 웹스터

1
@ 4r1y4n 제공된 코드가 실제로 병렬인지 여부는 코드로 수행하려는 작업에 따라 다르며 결합 된 스레드를 사용하여 컬렉션 전체에 분산 된 데이터를 집계하는 것과 더 관련이 있습니다. 스레드를 결합하고 있으며 이는 잠재적으로 데이터 "결합"을 의미합니다. 또한 병렬성이 반드시 동시성을 의미하는 것은 아닙니다. 이는 CPU에 따라 다릅니다. 스레드가 병렬로 실행 중일 수 있지만 기본 CPU에 의해 결정되는 순서에 관계없이 계산이 발생합니다.

22

다양한 솔루션을 살펴보십시오.

  1. join()API는 Java의 초기 버전에 도입되었습니다. JDK 1.5 릴리스 이후이 동시 패키지에서 몇 가지 좋은 대안을 사용할 수 있습니다 .

  2. ExecutorService # invokeAll ()

    주어진 작업을 실행하고, 모든 것이 완료되면 상태와 결과를 보유한 Future 목록을 반환합니다.

    코드 예제는이 관련 SE 질문을 참조하십시오.

    모든 스레드 풀이 작업을 수행하도록 invokeAll ()을 사용하는 방법은 무엇입니까?

  3. CountDownLatch

    다른 스레드에서 수행되는 작업 집합이 완료 될 때까지 하나 이상의 스레드가 대기 할 수 있도록하는 동기화 지원입니다.

    해, CountDownLatch는 주어진 카운트 초기화됩니다. await 메서드는 countDown()메서드 호출로 인해 현재 카운트가 0에 도달 할 때까지 차단되며 , 그 후 모든 대기 스레드가 해제되고 이후의 모든 await 호출이 즉시 반환됩니다. 이것은 원샷 현상으로 카운트를 재설정 할 수 없습니다. 카운트를 재설정하는 버전이 필요한 경우 CyclicBarrier 사용을 고려하십시오 .

    사용법은이 질문을 참조하십시오. CountDownLatch

    자체 스레드를 생성하는 스레드를 기다리는 방법은 무엇입니까?

  4. ForkJoinPool 또는 newWorkStealingPool ()실행자

  5. 제출 후 생성 된 모든 Future 객체를 반복 합니다.ExecutorService


11

Thread.join()다른 사람들이 제안한 것 외에도 Java 5는 실행기 프레임 워크를 도입했습니다. 거기에서 당신은 Thread물체로 작업하지 않습니다 . 대신 Callable또는 Runnable개체를 실행자 에게 제출합니다 . 여러 작업을 실행하고 그 결과를 순서에 맞지 않게 반환하는 특수 실행기가 있습니다. 그게 ExecutorCompletionService:

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

그런 다음 반환 take()Future<?>개체 가 더 이상 없을 때까지 반복적으로 호출 할 수 있으며 이는 모두 완료되었음을 의미합니다.


시나리오에 따라 관련 될 수있는 또 다른 사항은 CyclicBarrier입니다.

스레드 집합이 모두 서로가 공통 장벽 지점에 도달 할 때까지 기다릴 수있는 동기화 지원입니다. CyclicBarriers는 때때로 서로를 기다려야하는 고정 된 크기의 스레드 파티와 관련된 프로그램에서 유용합니다. 장벽은 대기중인 스레드가 해제 된 후 재사용 될 수 있기 때문에 순환이라고합니다.


거의 비슷하지만 여전히 몇 가지 조정을합니다. executor.submit를 반환합니다 Future<?>. 이 선물을 목록에 추가 한 다음 각 선물을 호출하는 목록을 반복합니다 get.
Ray

또한를 사용하여 생성자를 인스턴스화 할 수 있습니다 Executors. 예 : Executors.newCachedThreadPool(또는 유사)
Ray

10

또 다른 가능성은 CountDownLatch간단한 상황에 유용한 객체입니다. 미리 스레드 수를 알고 있기 때문에 관련 개수로 초기화하고 객체의 참조를 각 스레드에 전달합니다.
작업이 완료되면 각 스레드 CountDownLatch.countDown()는 내부 카운터를 감소시키는 호출 을합니다. 메인 스레드는 다른 모든 것을 시작한 후 CountDownLatch.await()차단 호출을 수행해야합니다 . 내부 카운터가 0에 도달하면 바로 해제됩니다.

이 개체를 사용 InterruptedException하면를 던질 수도 있습니다.


8

다른 스레드가 작업을 완료 할 때까지 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() «await 메서드는 현재 카운트가 0이 될 때까지 차단됩니다.

    생성 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.
    }
    

Threaded 클래스의 예제 코드 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 는 때때로 서로를 기다려야하는 고정 된 크기의 스레드 파티를 포함하는 프로그램에서 유용합니다. 장벽은 대기중인 스레드가 해제 된 후 재사용 될 수 있기 때문에 순환이라고합니다.
    CyclicBarrier barrier = new CyclicBarrier(3);
    barrier.await();
    
    예를 들어이 Concurrent_ParallelNotifyies 클래스 를 참조하십시오 .

  • 실행기 프레임 워크 : ExecutorService 를 사용하여 스레드 풀을 만들고 Future를 사용하여 비동기 작업의 진행 상황을 추적 할 수 있습니다 .

    • submit(Runnable), submit(Callable)Future Object를 반환합니다. future.get()함수 를 사용 하여 작업 스레드가 작업을 완료 할 때까지 주 스레드를 차단할 수 있습니다.

    • invokeAll(...) -각 Callable의 실행 결과를 얻을 수있는 Future 객체 목록을 반환합니다.

실행자 프레임 워크와 함께 실행 가능한 인터페이스, 호출 가능한 인터페이스를 사용하는 예제찾으십시오 .


@또한보십시오


7

당신은

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

이 for 루프 후에 모든 스레드가 작업을 완료했는지 확인할 수 있습니다.


4

스레드 객체를 일부 컬렉션 (예 : List 또는 Set)에 저장 한 다음 스레드가 시작되면 컬렉션을 반복하고 스레드에서 join () 을 호출 합니다.



2

OP의 문제와는 관련이 없지만 정확히 하나의 스레드와 동기화 (보다 정확하게는 랑데부)에 관심이 있다면 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 parallelStream을 사용하게되었습니다.

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

매우 간단하고 읽기 쉽습니다. 이면에서는 기본 JVM의 포크 조인 풀을 사용합니다. 즉, 계속하기 전에 모든 스레드가 완료 될 때까지 기다릴 것입니다. 내 경우에는 내 응용 프로그램에서 유일한 parallelStream 이었기 때문에 깔끔한 솔루션이었습니다. 하나 이상의 parallelStream이 동시에 실행되는 경우 아래 링크를 읽으십시오.

병렬 스트림에 대한 자세한 정보는 여기를 참조하십시오 .


1

몇 개의 스레드가 완료 될 때까지 기다리는 작은 도우미 메서드를 만들었습니다.

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

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;
        }
    }
}

주 스레드는 그룹의 모든 스레드가 완료 될 때까지 기다립니다.


-1

메인 스레드에서 이것을 사용하십시오 : while (! executor.isTerminated ()); 실행기 서비스에서 모든 스레드를 시작한 후에이 코드 줄을 넣으십시오. 실행자가 시작한 모든 스레드가 완료된 후에 만 ​​주 스레드를 시작합니다. executor.shutdown ();을 호출하십시오. 위의 루프 전에.


이것은 활성 대기로 CPU가 지속적으로 빈 루프를 실행하게합니다. 매우 낭비입니다.
Adam Michalik
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.